Как стать автором
Обновить
@Druuread⁠-⁠only

Пользователь

Отправить сообщение
> Проблема в тестируемости и потере детерминированности. Засунете вы в стор не промис, а декларативное описание этого промиса.

Ну да, промис же автоматом стартует при создании, так что описание промиса а не промис. Ну это не так важно, речь о том, что разницы-то никакой нет, тогда зачем огород городить? Ну стартанет твой промис потом, сразу после выполнения редьюсера. Ну и что? что это дает? Нам единственное что нужно — это чтобы следующий экшон не был обработан редьюсером до того, как будет обработан предыдущий, чтобы поток выполнения был линеаризован. И все.

> Вспомните саги — ровно то же.

Нет, сага — это свободная монада, интерпретатор.

> Тогда как контейнер не отвечает за ui.

Если он за ui не отвечает, то он и разметки содержать не должен, а значит — это attribute directive, а не component:
> Attribute directives—change the appearance or _behavior_ of an element, component, or another directive.
> Вы на что намекаете? Что удалять лишний враппер — это ок. А добавить — нет?

Это я намекая на ваши претензии вида: «это мне на каждую компоненту @Inheritance ставить? оО».

> Увы, есть.

Не совсем понял ваше объяснение. :host селектор проблему не решает?
> Synchronous state transitions caused by returning a new state from the reducer in response to an action are just one of all possible effects an action can have on application state.

АХахахахаха, :cry: :lol:
Ну я прям реально под стол сполз, и ребятам для того, чтобы прийти к этой очевидной идее надо было:

> used and followed the progression of Redux and the Elm Architecture, and after trying other effect patterns for Redux

Натуральная комедия. Ну и решение конечно такое же смешное — совать промис в стор. В чем проблема просто его дернуть? Нет, тогда ж мы пуристость потеряем, по-этому вместо этого мы засунем промис в стор, а потом его уже дернут :D
> Давайте я не буду вам цитировать дословно документацию и рассказывать почему редьюсеры должны быть чистыми и без сайд-эффектов в виде вызовов апи и выбросов других экшенов.

Не надо, я ее читал. Ничего кроме религии там по этому поводу нет. Штука вся в том, что _не имеет значения_ спавните вы экшон через мидлеваре или через редьюсер — это приводит к совершенно одинаковому control-flow вашего приложения со всеми вытекающими.
> Прекрасно — декларативно добавьте свою обертку в разметку. Это же не костыль.

Это мне для каждой директивы обертку добавлять? :D
;)

> Судя по офф-докам, так делать не стоит. Кроме того, использовать компонент по аттрибуту на уже существующем компоненте ангуляр не дает из-за конфликта имен.

А все просто — это используется в тех случаях, когда нам _обязательно_ надо избавиться от враппера, по-этому рекомендации из офф-доков не работают. И в этих случаях нам надо раскрывать компоненту в существующий тег — а значит он не будет другой компонентой :)

> Но остается последний вариант — когда нужно расширить кастомный компонент без обертки.

Если у нас кастомный компонент, то тогда нет проблем его обертывать.
> Нет «типа чистой» части и «типа грязной».

Ну нет, они по факту есть. Мы пишем код, обрабатывающий экщон, и этот код вполне осознанно автором редакса делится на две части — одна в редьюсерах, и другая в асинхронных экшонах, исполняемых через мидлеваре. Если разрешить из редьюсера бросать экшоны (в чем проблемы в общем-то никакой и нет по факту, это не меняет control-flow), то тогда асинхронные экшоны сведутся к обычным функциям а мидлеваре можно к хренам убрать. То есть в общем система сильно упрощается, и при этом ничем особо мы не жертвуем.

> Асинхронные реквесты общего вида (тот же процесс авторизации) прекрасно уживаются в сагах.

А результат авторизации как раз обычно должен быть доступен всему (почти всему) приложению, глобально, так что это, можно сказать, канонический пример, когда редакс оправдан.

> И вот их как раз удобно складывать в контейнер виджета, потому как на приложение в целом они не влияют.

Ну я об этом и говорил. Есть состояние, которое по смыслу _должно_ быть глобальным (те же данные залогиненого юзера) — и его менеджим редаксом. А то, что по смыслу локально и не требуется по каким-либо причинам класть в стор — пусть оно и будет локальным.
> Но если я в реакте «наследую» (расширяю через враппер со спредом, но без лишней дом-ноды) «вид» (компонент), то я получу новый компонент. Со старой моделью.

Ну кто вас какой гибкости лишает? Вы точно так же можете создать новый вид и связать его с расширением старой модели (с добавленным property «icon»), именно это и происходит в моем примере выше. Просто в реакте вы не делаете второй шаг (не описываете связывание), так как модель не выделена, связывать нечего, не с чем, а потому и не нужно. В реакте у вас есть возможность определить компоненту, как единую сущность. В ангуляре — нет. В ангуляре всегда есть разделение на вид и модель и всегда нужно описать их связь. И вы _можете_ сделать так, чтобы эта связь формировалась определенным образом автоматически (@ComponentInherit ваш), но это не является дефолтным поведением.

> Проблему я назвал — мне нужно расширить кнопку (окей, компонент строки таблицы) без доп. обертки

Ну а если я скажу, что мне надо тоже сделать кнопку, но с оберткой? Будут костыли уже в реакте. Я согласен, что иногда (весьма редко) действительно надо без оберток. Тогда компонент применяется атрибут-селектором и все, так что проблема полностью надуманна.
> Это с какой стати.

С простой стати. С точки зрения клиента, когда я задиспатчил экшон, то он ушел в редьюсер. Все, что происходит после диспатча экшона — происходит в редьюсере, о мидлеваре клиент не знает. Так что мидлеваре — это часть редьюсера де-факто. Просто вынесенная в отдельный кусок, так, что у нас есть «типа чистая» и «типа грязная» части. С чистой частью вот только все нормально, т.к. чистые функции хорошо композятся, а вот с грязной — выходит беда бедовая.

> DATA_FETCH_REQUESTED от DATA_FETCH_SUCCEDED и продолжают

Асинхронные реквесты вообще в редакс пихать не надо, только если уж совсем приперло (надо кеширование, разделять контент между многими компонентами, а пропсы прокидывать лень и т.п.). Их надо оставлять в локальном стейте компонент. То же, как оно сейчас многие делают (хреначить по десяток экшенов на каждый реквест, а их в приложении сотни… эту кашу поддерживать не проще чем программу на брейнфаке) — откровенная шизофрения.

> Зачем вам вообще этот подход?

Так если доработать напильником и не упарываться религией автора — то вполне удобно. Ну и к слову, поскольку глобальное состояние — это само по себе очень серьезный антипаттерн, я туда просто стараюсь ничего не совать без крайней необходимости (в противовес идиотичному «перенесем все в редакс»).

> Вы пробовали mobx?

Ой, фу.
> Вы считаете целесообразным лепить InheritMetadata в каждом компоненте, чтобы помочь наследованию сделать дело?

В реакте все постоянно так делают и никаких проблем это не вызывает. Это во-первых. Во-вторых — метаданные из Component наследоваться и не должны, так что нет, _я_ не предлагаю. Это _вы_ предлагаете такое поведение. Я вам лишь указал, как вы можете его достичь — это делается абсолютно бесплатно, переопределением декоратора.

> Что мешает template, style и компанию складывать в class properties?

Ничего не мешает. Но это не будет работать.

> Не наследуются все метаданные.

А зависимости, видать, инжектятся в потомках за счет святого духа? А инпуты /оутпуты базового класса откуда берутся? Или вы не в курсе, что это все — тоже метаданные?

> Ну то есть про дизайн и проблемы вы согласны?

Вы же пока не назвали проблем.

> Святая корова, да почему??

Потому что поведение будет неопределено. И определить его каким-либо осмысленным образом нельзя. Вы как-то пропустили мой основной тезис из предыдущего поста. Вид и модель — это две разные сущности. Наследуя класс компонента — вы наследуете модель. А вид — он отдельно, он вообще не класс, даже.

Вот у вас в реакте модели как бы нет, есть только вид (и потому он же — модель). Вы пишите свой компонент, пишите — потом обнаруживаете, что в нем слишком много логики. Вы выносите эту логику со всей работой с состоянием в отдельный класс — теперь у вас есть модель и есть вид, при этом модель ничего не знает о виде, и если вы отнаследуете модель — то не получите нового компонента. Точно так же, вы не получаете нового компонента при наследовании модели в ангуляре. И _не должны_.

Далее вы хотите сделать все более молодежно и модно. По SOLID. У вас ведь вид привязан к модели — а это нехорошо. Вы их развязываете — теперь у вас вид зависит не от модели, а от ее интерфейса, а конкретная модель инжектится. А какая модель? Неизвестно — теперь вид ничего не знает о конкретной модели. Значит, нужна конфигурация, и в ней у вас будет написано что-то вроде: «модель Х рендерится видом Y». И если вы хотите отнаследовать одну компоненту от другой — то это сразу не заработает, вам надо пойти и добавить соответствующее вхождение в конфиг, чтобы связать вид с моделью. Как в ангуляре.

Потом проходит некоторое время и оказывается, что ваша конфигурация превратилась в огромную портянку. Да и вообще — прыгать туда-сюда, чтобы узнать что там к чему инжектится и с чем связано — не совсем удобно. Вы распиливаете вашу конфигурацию на куски — по куску на модель — и каждый кусок, соответствующий конфигурации модели, храните вместе с моделью. А конфигуратор потом собирает из этих кусков исходный полный конфиг. И у вас получается архитектура ангуляра. В ангуляре все вышеобозначенные шаги сделаны — потому что это фреймворк. У него есть архитектура. Реакт — это библиотека, в ней архитектуры нет. Предполагается, что ее надо создавать самому для конкретного приложения. И когда вы ее создадите, то точно так же у вас вид будет отделен от модели и будет отдельно наследоваться…
> Свести типы экшенов (FSA) в один discriminated union с общим ключом type. В редьюсере switch/case сможет выполнить сужение типа в зависимости от type в кейсе.

Ну так вы тогда возвращаетесь к классическому редаксу, то есть ни createAction (), ни createReducer из примера по вашей ссылке использовать нельзя (createReducer ведь идет _вместо_ switch/case).

> Но будут асинхронные процессы обработки синхронных экшенов, например саги.

Саги еще имеют более-мене человеческое лицо, но ситуация принципиалньо не меняется — у вас в редьюсер может прилететь обычный экшон, с которым потом выполнится чистая функция по изменению стора, а может — непонятная асинк-хрень, которая грязно спавнит другие асинк-хрени. В итоге тот факт что редьюсер — чистый, вам ничего не дает, ведь логика мидлеваре входит в логику редьюсера де-факто. Это перекладывание из левого кармана в правый. Решается все на самом деле просто — достаточно разрешить спавнить экшоны из редьюсеров (тогда экшоны остаются экшонами (событиями), а редьюсеры — редьюсерами (реакциями)), но это нарушает религиозные убеждения автора библиотеки.
> И позвольте узнать, какая проблема у middleware?

Оно в сущности своей кривое и костыльное. Вообще, изначально проблема в том, что и простые экшоны и асинхронные являются экшонами. Сам факт существования сущности «асинхронный экшон».

> Flow в помощь.

Ну так для этого код должен быть такой, чтобы его можно было без болей типизировать. У вас результат createAction принимает data какого-то произвольного типа, как сделать, чтобы он был конкретного типа (пейлоад вашего экшона), и чтобы в редьюсере потом при определении соответствующей функции этот тип автоматически использовался? Я не вижу способа. Это и есть основная проблема дизайна редакса (ну, кроме упомянутой выше с асинхронными экшонами). Есть экшон криейтор, есть редьюсер, они имеют единую зависимость (чисто по смыслу) — это экшон, но его по факту в коде по дефолту нет. В итоге эту проблему надо решать — либо отдельно выписывать экшон и на него явно ссылаться (вариант дефолтного редакса), либо делать какюу-то обвязку, которая прибьет редьюсер к экшону (как у меня выше).
> Вы в упор меня не слышите, и складывается впечатление, что специально. Целесообразно использовать инструмент для решения задачи, но нецелесообразно лепить костыли для решения проблемы в дизайне.

То что мы обсуждаем — и есть обычное использование инструмента для решения задачи. Мне непонятно, почему вы готовы писать обычные функции в реакте, но против того, чтобы делать то же самое в ангуляре в точно том же самом контексте.

> Вот что мешало включить всю конфигурацию декоратора в тело класса?

То, что это просто нельзя сделать сколько-нибудь осмысленным образом. Те метаданные, что имеет смысл наследовать, наследуются. А те, что наследовать нельзя — не наследуются. Ни стили, ни темплейты, ни другие подобные вещи наследовать просто нельзя, это чревато серьезными проблемами. То есть, эту фичу невозможно сделать так, чтобы она не была заведомо поломанной.

> Мнимая «переиспользуемость» классов отдельно от этих декораторов?

Вот, кстати, да. Если желаете — можете вынести конфигурацию декоратора и переиспользовать ее в разных классах (наследниках, например). И никаких «кастомных декораторов».

> Не притворяйтесь, вы прекрасно поняли задачу.

Нет, не понял. Ваш пример на реакте был уже позже, я когда его увидел — то переделал.

> Мой посыл в том, что дизайн кривой, пользоваться неудобно, проблемы есть.

Удобство — это субъективная категория. Я не вижу какой-либо разницы по сравнению с реактом. Мне, например, jsx не удобен и кривое разделение ответственности (ну тут, собственно, проблема в том, что реакт — не фреймворк, а библиотека, и для нормальной работы так же как и редакс предполагается дорабатывать его напильником, так что все так и должно быть).

> Нет, не сродни.

Это ваше мнение.

> Мы говорим о наследовании, а не о композиции. Если ButtonWithIcon наследует Button, то подразумевается что все поведение, вплоть до работы с EventEmitter наследуется, а не копипастится.

Во-первых, оно и наследуется. Не наследуется шаблон, потому что его нельзя осмысленно наследовать, а ивентэмиттеры и т.п. — наследуются, я же об этом выше уже говорил. Во-вторых, нет, это именно композиция. Вы пишите одну компоненту, в которой вызывается другая компонента, естественно вы передаете во вторую компоненты требуемые ею аргументы, точно так же как вызывая функцию из другой функции. Это происходит на уровне controller/view (шаблона). А на уровне модели (ComponentClass) вы наследуете. В реакте модель гвоздями прибита к вью (их можно разделить, вообще, но сам реакт по дефолту не о том), в ангуляре — это две разные и независимые сущности. По-этому наследуются метаданные которые относятся к модели (соответствующие декораторам членов), но не наследуются те, что относятся к виду Это же простая и ясная структура. В чем проблема?

> Что дизайн оправдан? Нет.

Это ваше мнение. Единственную вашу объективную претензию мы рассмотрели, если других таких же нету — я не вижу смысла продолжать. Потому что «здесь вызов функции — использование инструмента, а там — костыль» или «оно врапает а я хочу чтобы не врапало» — это не разговор.
Типизации пейлоада же там нет? Значит, счастье не достигнуто. Ну и проблема middleware остается.
> Но пока то, что я вижу с Ангуларом — визуально очень ужасно в сравнении с очень изящным Реактом. Может, если пописать лично, то впечатление будет иное?

Не думаю, обычно если что-то с виду не нравится — то оно не нравится. У меня вот как кровь текла из глаз от jsx'a, так и продолжает течь, и ничего не сделаешь.

> Это и выяснять не надо было. Разговор был о целесообразности, а не о классификации.

Ну вы согласны тогда, что писать свои метаредьюсеры нецелесообразно? И hoc в реакте — тоже? Если да, то тогда ваша позиция понятна. Если нет — то тогда тут наблюдается явная непоследовательность.

> По кругу мы ходим из-за проблем ненаследования метаданных, а не наследования inputs/outputs.

Тогда к чему вы приплели inputs/outputs?

> вам нужно пойти во все наследники и руками пробросить это все в шаблоне (как в случае с пробросом clicked).

Это сродни претензии к тому, что если вы в функцию добавили аргумент, то надо везде, где эта функция вызывается, добавить этот аргумент. Я тут ничего плохого не вижу.

> Кроме того, если у кнопки есть стили, а в 99% случаев они есть, то они не наследуются.

Так это правильно. Они легко могут сломаться, если их отнаследовать.

> Совсем не то же самое. Иконка должна быть внутри my-button.

Так бы сразу и сказали, я же не могу мысли читать:
@Component({
    selector: "my-button",
    template: `<button (click)="clicked.emit()"><ng-content></ng-content></button>`,
})
export class MyButtonComponent {
    @Input() name: string;
    @Output() clicked = new EventEmitter;
}

@Component({
    selector: "my-button-with-icon",
    template: `<my-button (clicked)="clicked.emit()" [name]="name"><span>{{ icon }}</span></my-button>`,
})
export class MyButtonWithIconComponent extends MyButtonComponent {
    @Input() icon: string;
}

так?
> Никаких декораторов, костылей и подпорок — просто функции.

Декоратор — и есть просто функция. Мы, вроде, уже это выяснили?

> Это все очень и очень здорово. Только вот, чтобы получить кнопку с иконкой, ни декораторы, ни HOC не используются:

ну вот мой вариант был:
@Component({
    selector: "my-button",
    template: `<button (click)="clicked.emit()">{{ name }}</button>`,
})
export class MyButtonComponent {
    @Input() name: string;
    @Output() clicked = new EventEmitter;
}

@Component({
    selector: "my-button-with-icon",
    template: `<span>{{ icon }}</span><my-button (clicked)="clicked.emit()" [name]="name"></my-button>`,
})
export class MyButtonWithIconComponent extends MyButtonComponent {
    @Input() icon: string;
}

то же самое ведь?

> Мы ходим по кругу.

Нет, они сами по себе наследуются. Без «кастомных декораторов» и всего такого:
export class MyButtonWithIconComponent extends MyButtonComponent {
    @Input() icon: string;
}
> Redux придумывали не для того, чтобы с ним боролись и пытались «вернуть все на свои места» запаковывая этот бойлерплейт обратно.

Так я этот бойлерплейт не запаковываю — он есть, я могу использовать любой его фрагмент. Я только не пишу его руками (но если будет надо в каком-то специфическом случае — могу и написать). Просто в оригинальном виде приложение на редаксе поддерживать невозможно в принципе. Собственно, сам Абрамов тоже пишет, что редакс — легковесная библиотека, которая предполагает использования разного рода обвязок для конкретного проекта. Примеры он на StackOverFlow приводил.
> Еще больше не поверите, использую ngrx/store

Я тоже использую ngrx/store. При этом у меня есть обвязка из (только не пугайтесь)… кастомных декораторов, которая уменьшает типичный редаксовский бойлерплейт в разы ;)
    @Action()
    actionName(
        @Payload("argName") argName,
    ) {
        return { ...this.state, prop: argName };
    }

генерит сразу action-константу, action-creator, метод с вызовом dispatch'a и соответствующий кусок редьюсера. И, да, в отличии от декораторов класса, декораторы аргументов и методов, конечно, уже не просто функции.
> Да потому-что придется копипастить inputs/outputs из my-button в my-button-with-icon.

Инпуты/аутпуты наследуются. У меня же выше был пример с наследованием, там это видно.

> Кастомный декоратор — это сторонние средства.

Декоратор — это _просто функция_. Вот это:
@Component({ ...props })
export class FooComponent {
    
};

ровно то же самое, что и вот это:
class Foo {

};

export const FooComponent = Component({ ...props })(Foo);

Когда вы на реакте применяете какую-то ф-ю к компоненту — вы фактически и используете декоратор :)
//так вы пишите:
const EnhancedComponent = higherOrderComponent(WrappedComponent);

//а можно записать с синтаксисом декоратора:
@higherOrderComponent
export class WrappedComponent {
    
} 

то есть «определить и использовать в реакте HOC» и «определить и использовать кастомный декоратор» — это _одно и то же_. HOC из реакта — это _и есть_ декораторы классов.

> что стандартный Component использовать нельзя, нужно использовать кастомный. И мы возвращаемся к огромному бессмысленному мануалу.

Команда же может запомнить, что надо юзать higherOrderComponent выше? :)

> Окей, тут соглашусь

Значит, по факту декларативности мы вопрос утрясли.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность