Комментарии 26
Архитектура Redux. Да или нет?
Да, если ваше приложение, мессенджер, продающий одностраничник или TODO лист, и нет во всех остальных случаях imho… :)
Для нас выходом из этого кошмара стал MobX + наша библиотека управления крупными графами моделей: github.com/wearevolt/mobx-model, которая одинаково хорошо подходит как для маленьких, так и очень крупных проектов.
1) Почему мы не можем хранить состояние списка друзей в синглтоне FriendsService? При таком подходе данные всегда будут синхронизированы во всех компонентах, использующих список друзей.
2) Почему запросом данных и передачей их в хранилище занимается компонент списка друзей? Что, если у нас два компонента для рендеринга этого списка — один в разделе «Мои друзья», другой в модальном окне «Поделиться записью», выглядят совершенно по-разному. Каждый компонент должен проверять наличие данных в состоянии, и грузить их, если они отсутствуют? Дублирование кода же.
3) У нас же real-time приложение. По сокетам прилетает событие, мол, новый друг добавлен в список, или удалён из него. Где это обрабатывать? Самый логичный вариант — этим должен заниматься тот самый синглтон FriendsService.
В чём преимущество Redux перед описанным мной подходом?
Кто-нибудь, пожалуйста, ответьте мне на эти вопросы. Я чувствую себя неполноценным членом Front-end сообщества из-за непринятия Redux.
1) Почему мы не можем хранить состояние списка друзей в синглтоне FriendsService? При таком подходе данные всегда будут синхронизированы во всех компонентах, использующих список друзей.
При условии использования PureComponent в качестве контейнера для компонентов это частный случай редаксового стора.
2) Почему запросом данных и передачей их в хранилище занимается компонент списка друзей? Что, если у нас два компонента для рендеринга этого списка — один в разделе «Мои друзья», другой в модальном окне «Поделиться записью», выглядят совершенно по-разному. Каждый компонент должен проверять наличие данных в состоянии, и грузить их, если они отсутствуют? Дублирование кода же.
Используйте один контейнер для обработки жизненного цикла и два разных компонента для отображения и подписки на события.
3) У нас же real-time приложение. По сокетам прилетает событие, мол, новый друг добавлен в список, или удалён из него. Где это обрабатывать? Самый логичный вариант — этим должен заниматься тот самый синглтон FriendsService.
И еще синглтон оповещений. И синглтон для иконки на вкладке браузера. И еще парочка синглтонов, символизирующих больное воображения менеджера продукта. Возможно мне просто не везло с проектами.
Если сравнивать с вашим подходом — Redux может быть удобен, когда в вашем проекте сложная логика взаимодействия сервисов.
Можно подумать сам редукс не является синглтоном. А можно пример сложной логики?
Не знаю насколько сложно. На Redux мне было достаточно удобно расшаривать логику из различных мест. На первом Angular тоже делал три года назад схожую вещь — было менее удобно. Возможно еще использование TypeScript помогло.
Из более раннего. Пользовательское облачное хранилище. Внедрение показа какой-то фичи на основе A/B теста, промо настроек, платных настроек, положения в пользователя внутри этого самого хранилища, обработка ситуации, когда пользователь вообще не залогинен. Вроде вообще не сложно, но определения в виде чистых функций удобнее покрывать тестами. Еще инициализировать это все удобнее на сервере тем же кодом, что и на клиенте. Еще очень хорошо получилось интегрироваться со старым кодом, использующим не React, а свою библиотеку.
Что характерно — нигде Redux не тормозил. MobX, наверное тоже бы не тормозил. Вот на предыдущем месте работы соседний отдел делал вебверсию почтового клиента. У них тоже ничего не тормозило.
1) Почему мы не можем хранить состояние списка друзей в синглтоне FriendsService? При таком подходе данные всегда будут синхронизированы во всех компонентах, использующих список друзей.Из моего опыта разработки клиентской части, что в геймдеве, что в вебе данные удобнее хранить в одном месте (классе) и не писать там больше дополнительного функционала, кроме самого основного (отписки/подписки). И кода меньше и он более однотипный становится.
И для этого redux не требуется.
3) У нас же real-time приложение. По сокетам прилетает событие, мол, новый друг добавлен в список, или удалён из него. Где это обрабатывать? Самый логичный вариант — этим должен заниматься тот самый синглтон FriendsService.Допустим как и в первом случае, FriendsService только принимает (и отправляет) данные от сервера и сохраняет их в хранилище.
В простых и средних приложениях так норм делать. FriendsService получает событие с данными и обновляет Store. Я пока не сталкивался со случаями, где такой подход вызывал бы проблемы.
В больших проектах наверное было бы лучше функции обновления стора вынести вне FriendsService. Т.е. FriendsService только принимает данные и вызывает функции, которые получают данные от FriendsService, преобразовывают их в нужный формат и обновляют хранилище. Такой подход немного похож на редьюсеры redux. Но я сомневаюсь в необходимости этого, т.к. формат принимаемых данных зависит от сервера. Получается что конкретная функция записи данных в стор, за редким исключением может работать только с форматом данных, который используется только конкретным методом FriendsService-а. Тогда зачем их разделять, если они связаны?
Из моего опыта разработки клиентской части, что в геймдеве, что в вебе данные удобнее хранить в одном месте (классе) и не писать там больше дополнительного функционала, кроме самого основного (отписки/подписки)
А можно конкретные примеры — почему удобнее? Чем централизованное хранилище лучше децентрализованного, разбитого на модули?
К примеру, наш FriendsService подписан на socket событие friends:state, в котором прилетает состояние списка. Мы стоим перед выбором — записать список в глобальный стор, либо в this.state.
Почему мы должны выбрать глобальный стор для этой цели?
Также интересует вопрос — как быть, если приложение разделено на изолированные модули, подгружаемые с помощью LazyLoading? Только что загруженный модуль должен использовать глобальное состояние для хранения своих данных?
К примеру, наш FriendsService подписан на socket событие friends:state, в котором прилетает состояние списка. Мы стоим перед выбором — записать список в глобальный стор, либо в this.state.Если у вас только один компонент использует эти данные, можете использовать this.state. Если же несколько компонентов и им нужно взаимодействовать друг с другом, а также изменять общие данные без отправки на сервер, то тут уже пригодиться глобальный стор.
Почему мы должны выбрать глобальный стор для этой цели?
Просто глоток свежего воздуха. Болейрплейта практически нет. При этом все фичи редакса не теряются, т.к. это всего лишь удобная обертка.
Главный плюс — не нужно писать кучу ACTION_TYPES, Action Creators, кучу switch в редьюсерах.
Советую глянуть видео и/или пощупать.
Насчёт кучи switch посмотрите https://www.npmjs.com/package/redux-create-reducer (часто createReducer сами пишут в проектах, благо занимает пару строк).
Посмотрел Rematch, попробую его, если нужно будет сделать какой-нибудь маленький проектик.
Единственное, что приходит в голову — пригодится для общения с WebWorker.
А вот если в программе каждый action обрабатывается ровно одним редьюсером — то весь redux и правда вырождается в кучу бойлерплейта, которой можно было бы избежать если выбрать какой-нибудь mobx.
Суть в том, что этот самый action вовсе не обязан быть обработан ровно одним редьюсером.
В предложенном выше решении (rematch) он и не должен быть обработан ровно одним редьюсером — то есть вам никто не мешает написать свои дополнительные редьюсеры на нужный action и добавить их, все прекрасно будет работать (и имеющийся код переписывать как-либо будет тоже не нужно), т.к. процесс спавна экшона и его обработки проходит в рамках стандартного redux-овского потока управления. Фактически, там просто генерируется универсальный редьюсер, по особому обрабатывающий экшоны определенного формата, не более того.
Но вот в чем штука — я сам уже больше года использую в ангуляре аналогичную самописную обертку над ngrx (± детали и поправка на то, что ngrx немного отличается по апи от редакса, а так почти то же самое), и попробуйте угадать, сколько раз приходилось писать несколько редьюсеров на 1 экшон? Верно — ноль :)
То есть в 99 случаях из ста мы получаем упрощение (экшон с редьюсером и пейлоадом действительно вырождаются в просто функцию с телом и аргументами), а в 1 оставшемся — ну можно написать как предполагает чистый redux way, предложенный подход полностью совместим. И даже если у вас не 99-1, а 90-10 или даже 80-20 соотношение, концептуально это ничего не меняет, и выгода от использования подобного решения будет.
ЗЫ: единственно что не совсем уверен как в rematch типизация работает, типы на вид адовенькие там
Если у вас настолько маленькие приложения, что «бойлерплейт» лишний
Любой бойлерплейт является лишним в любом приложении. И в большом приложении он легко может быть даже "более лишним", чем в маленьком. Потому что одно дело поддерживать 200 строк бойлерплейта, и совсем другое — 20000.
Архитектура Redux. Да или нет?
MobX
Все что вам нужно знать о Redux: вам он не нужен, если у вас маленькое и простое приложение и он вам не подойдет, если у вас большое и сложное приложение. В сложных случаях, для хранения данных лучше использовать графовые структуры, а в простых проще написать свой pub/sub на коленке: < 100 строк на все. Так что — нет.
Архитектура Redux. Да или нет?