Как стать автором
Обновить

Комментарии 15

Сколько не смотрю на весь этот набитый бойлерплейтом редакс, а никогда не мог понять глубинную логику разделения на action и reducer. Идеология выглядит красиво — мол, экшены фигачат сообщения вида «что случилось», а редьюсеры уже стейт правят. На практике же у нас одна ответственность вида «обновить стейт определенным образом» (на этом всё остальное приложение держится, собственно, ожидая, что стейт будет обновляться определенными способами) поделилась на два несвязанных куска — экшн, который сам по себе это ничего не значащая тыква, и редьюсер, который сам по себе тоже ничего не значащая тыква, но если она возвращает что-то интересное в ответ на определенные строки в action.type, то всё это наконец-то цепляется друг за друга и начинает вертеться.

Вот только почему это всегда выглядит очень лишним усложнением на ровном месте?

Это всё имеет смысл, если один 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. Ну или дебажится в рантайме, в крайнем случае. Но это очень далеко не идеал.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Ну да, это примерно то, что вменяемый человек будет колхозить для реализации MVVM в реакте, если вдруг по каким-то причинам оно ему надо будет.
Другое дело, что я не уверен, что оно кому-то особо надо. Я пока не разубеждён в том, что для реализации хорошей M и управления ей надо использовать что-то отличное от реактивного программирования. С реактивным программированием обычно выходит всё очень чисто и просто для понимания (когда есть представление, как вообще реактивное программирование устроено). В идеале всю модель вообще можно свести к псевдо-POJO, с которым вся работа проходит через банальные геттеры и сеттеры, но у которого под капотом этого завёрнута вся логика.
Тяжелый момент, как обычно, будет в том, как бы эту модель с логикой прицепить к рендеру без костылей. У реакта есть определенные проблемы в этом плане (например, MobX их решил, но костылями).

По мне все круто, только для динамической загрузки редьюсеров и саг я бы заюзал microsoft/redux-dynamic-modules

В своих проектах стараюсь использовать компоненты-контейнеры, которые отвечают за логику получения данных, а также презентационные компоненты, которые получают необходимые данные из контейнера и отображают их. Такой подход позволяет разделить ответственность и код становится более читабельным. Также одни и те же данные могут понадобиться в нескольких местах, например получение списка новостей для страницы «Новости» и для компонента «Последние новости», который скажем показывается на «Главной странице». Компонент-контейнер в таком случае позволит избежать дублирования кода.

Имхо, если проект еще не ушел в тяжелое легаси, лучше разрюхать react new context api и react hooks, а не городить очередной костыль для redux.
А еще рекомендую использовать typescript.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории