Комментарии 15
Вот только почему это всегда выглядит очень лишним усложнением на ровном месте?
Это всё имеет смысл, если один action обрабатывается несколькими редьюсерами, прямо или косвенно (через мидлвари).
Внутренняя же логика приложения из нескольких редьюсеров — это вы просто берете и очевидную ветвящуюся логику (если А, делаем B+C, иначе делаем B+D) пихаете куда-то в гораздо менее очевидное место (коллекция редьюсеров в сторе на данный момент). Проще говоря, меняете очевидный код на неочевидный — было ветвление по действиям, стало ветвление для разнообразного подписывания редьюсеров в стор. Что само по себе всегда очень плохо.
Если я правильно понял ваш комментарий (несколько раз перечитал), то ваш очевидный код является сильно связанным. Какой-то простой компонент типа кнопки должен знать про A, B, C и D.
Если у нас есть стор и его обновления (неважно, редакс это или не редакс), и архитектура более-менее MVC (вернее, обычно таки MVP или MVVM, но это в общем-то одни и те же яйца с разных ракурсов), то сфигали логика будет прибита к компонентам? Вы не представляете других мест, куда её можно положить, что ли?
Всё, что я описал — это вариант, в котором вместо двух раздельных «делаем действие» и «редьюсеры обновляют стор» происходит только одно «делаем действие» (с необходимыми ветвлениями внутри).
Ну вот простой компонент был с ответственностью вызвать изменение стора B при нажатии мышью на кнопку. пришло новое требование "если нажата левая кнопка, то дополнительно вызывать C, если правая, то дополнительно вызывать D". Где вы будете это делать?
В сторах подобных редаксовым, с разделениями на экшены и редюсеры (события и их обработчики, по сути) мы просто добавим новый обработчик для существующего экшена, а может даже два новых обработчика, которые будут игнорировать не свою кнопку. Без разделения, как я понимаю, в сторе был метод B (причём B скорее всего что-то из предметной области, ане onMyButtonClick, который вызывался в onClick. Теперь нам нужно будет создавать именно onMyButtonClick скорее всего, в котором анализировать какая кнопка нажата. Ну или создавать метод типа doBwithCorD(bool withC, bool withD) и передавать соотвествующие флаги. Так?
Без разделения, как я понимаю, в сторе был метод B (причём B скорее всего что-то из предметной области, ане onMyButtonClick, который вызывался в onClick. Теперь нам нужно будет создавать именно onMyButtonClick скорее всего, в котором анализировать какая кнопка нажата. Ну или создавать метод типа doBwithCorD(bool withC, bool withD) и передавать соотвествующие флаги. Так?
Если «новое требование» относится к изменению предметной области (то есть, действия остались теми же самыми, только их суть теперь другая) — то код компонента вообще не меняется, в нем так и остаётся «метод В», только «метод В» теперь будет работать иначе. Это, конечно, всё зависит от уровня охвата — для цельного SPA скорее всего будет не так (а для какого-то компонентного куска про кнопки и нажатия на них — возможно).
Если же «новое требование» к предметной области отношения не имеет — в компонент (в обработчик onClick, если точнее) вносится логика по вызову C или D в зависимости от условий. Если компонент один — то прямо в него, почему бы и нет. Если компонентов много — куда-то в общее для этих компонентов место. Это ровно то же самое, что вы сделаете, если «просто добавите новый обработчик или два», только явно изложенное в коде, а не зарытое во внутренней логике редьюсеров и в строках, подписывающих их в стор.
В парадигме action-reducer, если вам по прежнему надо где-то «просто» нажимать на кнопки, а где-то с вывертом — вы скорее всего будете заводить новый тип action «с вывертами», и семантически это будет то же самое, что и onMyButtonClick. Ну и опять же вам придётся написать код, который позаботится о том, что в action будет положена нажатая кнопка, чтоб потом редьюсеру было, что проверять. При этом у вас нет никакой особой свободы творчества, и вы даже так просто не дернете подряд два экшна из onClick (в духе doSimpleClick(); doComplexClick(ev)) — для этого вам сначала придётся пойти и разобраться, нет ли в них какой-нибудь асинхронности, которая может породить нежелательные обновления UI, если отработает в произвольном порядке.
Я скорее про ситуацию, когда кнопка вызывала какие-то изменения в модели предметной области, а новые требования говорят, что дополнительно к этим изменениям в модели нужно делать одно из двух других в зависимости от условия какого-то. Чисто UI задача для конкретного компонента — уменьшение количество кликов пользователем в какой-то ситуации. И я не буду заводить отдельный экшен, просто добавлю поле в существующий. Аналогично вызову метода с параметром.
И я не буду заводить отдельный экшен, просто добавлю поле в существующий. Аналогично вызову метода с параметром.
… тем самым еще больше скрывая и запутывая логику. I rest my case. Во имя архитектуры нам для выяснения логики работы конкретного действия теперь надо быть в курсе подписок редьюсеров в данный момент рантайма (хорошо, если оно там особо не пляшет в рантайме, а более-менее единожды задаётся) и в курсе полного содержимого конкретного action. Ура?
PS: И не поймите меня неправильно, я не считаю такой подход чем-то плохим самим по себе. Это работает, это пишется, и приемлемо читается, если вы прониклись парадигмой action-reducer. Ну или дебажится в рантайме, в крайнем случае. Но это очень далеко не идеал.
Другое дело, что я не уверен, что оно кому-то особо надо. Я пока не разубеждён в том, что для реализации хорошей M и управления ей надо использовать что-то отличное от реактивного программирования. С реактивным программированием обычно выходит всё очень чисто и просто для понимания (когда есть представление, как вообще реактивное программирование устроено). В идеале всю модель вообще можно свести к псевдо-POJO, с которым вся работа проходит через банальные геттеры и сеттеры, но у которого под капотом этого завёрнута вся логика.
Тяжелый момент, как обычно, будет в том, как бы эту модель с логикой прицепить к рендеру без костылей. У реакта есть определенные проблемы в этом плане (например, MobX их решил, но костылями).
По мне все круто, только для динамической загрузки редьюсеров и саг я бы заюзал microsoft/redux-dynamic-modules
Имхо, если проект еще не ушел в тяжелое легаси, лучше разрюхать react new context api и react hooks, а не городить очередной костыль для redux.
А еще рекомендую использовать typescript.
React. Lazy loading