Pull to refresh

Comments 26

Начал читать, ожидая увидеть что-то эдакое интересное, а тут только рассказали, что есть Redux и… статья закончилась…
Архитектура Redux. Да или нет?

Да, если ваше приложение, мессенджер, продающий одностраничник или TODO лист, и нет во всех остальных случаях imho… :)
UFO just landed and posted this here
Может быть, наоборот? Да — если у ваш дешборд. Нет — если у вас что-то простое типа TODO листа или «продающего одностраничника»
Redux ужасно себя проявляет в понастоящему крупных проектах. Помимо раздутой кодовой базы, вы начинаете сталкиваться с проблемами когда огромная стора еле ворочается на каждый чих. А если у вас структура данных представляет из себя ссылочный граф, а не дерево, то с Redux вы приехали.

Для нас выходом из этого кошмара стал MobX + наша библиотека управления крупными графами моделей: github.com/wearevolt/mobx-model, которая одинаково хорошо подходит как для маленьких, так и очень крупных проектов.
Я уже который год пытаюсь осознать смысл Redux. После прочтения вашей статьи мои сомнения в нём только усугубились.

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, наверное тоже бы не тормозил. Вот на предыдущем месте работы соседний отдел делал вебверсию почтового клиента. У них тоже ничего не тормозило.
Хочу прокомментировать. Но я не про Redux буду, т.к. не пользуюсь я им. Просто про схожую архитектуру action -> store -> ui components -> action

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. Если же несколько компонентов и им нужно взаимодействовать друг с другом, а также изменять общие данные без отправки на сервер, то тут уже пригодиться глобальный стор.
Недавно открыл для себя Rematch.
Просто глоток свежего воздуха. Болейрплейта практически нет. При этом все фичи редакса не теряются, т.к. это всего лишь удобная обертка.
Главный плюс — не нужно писать кучу ACTION_TYPES, Action Creators, кучу switch в редьюсерах.
Советую глянуть видео и/или пощупать.
Если у вас настолько маленькие приложения, что «бойлерплейт» лишний — то вам не надо использовать ридакс, это же очевидно. Если ваш техлид, техдир, тимлид, etc., настаивают на ридаксе там, где он не нужен — то тут тоже всё очевидно: проблема не в ридаксе. Если у вас просто наследие, то тут тоже всё просто. Ведь для всякой задачи подходит свой инструмент, а ридакс не серебряная пуля.

Насчёт кучи switch посмотрите https://www.npmjs.com/package/redux-create-reducer (часто createReducer сами пишут в проектах, благо занимает пару строк).

Посмотрел Rematch, попробую его, если нужно будет сделать какой-нибудь маленький проектик.
Возможно, у меня мало опыта с redux. Но его фишку с отдельной сущностью action, которая простой объект, я понять не могу. Какой-то переусложненный опосредованный способ дернуть метод, который может что-то в сторе поменять. Прям SQL или HTTP запрос какой-то. Но там понятно, разные среды исполнения. В SPA приложении-то зачем? Почему не напрямую дернуть метод?
Единственное, что приходит в голову — пригодится для общения с WebWorker.
Суть в том, что этот самый action вовсе не обязан быть обработан ровно одним редьюсером. Он может быть обработан двумя разными редьюсерами, может быть трансформирован мидлварью, может быть вовсе никем не обработан.

А вот если в программе каждый action обрабатывается ровно одним редьюсером — то весь redux и правда вырождается в кучу бойлерплейта, которой можно было бы избежать если выбрать какой-нибудь mobx.
Когда что угодно может быть обработано чем угодно (event bus) — сложно становится искать постоянно концы и гарантировать что никакое событие не потеряется и когда-нибудь вообще возникнет.

Тоже возникла такая мысль, что подход тот же, что и с обычными событиями. Слишком гибкая архитектура, сложно понять и отладить. Неявные логические связи, потеря причинно-следственной связи.

Суть в том, что этот самый action вовсе не обязан быть обработан ровно одним редьюсером.

В предложенном выше решении (rematch) он и не должен быть обработан ровно одним редьюсером — то есть вам никто не мешает написать свои дополнительные редьюсеры на нужный action и добавить их, все прекрасно будет работать (и имеющийся код переписывать как-либо будет тоже не нужно), т.к. процесс спавна экшона и его обработки проходит в рамках стандартного redux-овского потока управления. Фактически, там просто генерируется универсальный редьюсер, по особому обрабатывающий экшоны определенного формата, не более того.
Но вот в чем штука — я сам уже больше года использую в ангуляре аналогичную самописную обертку над ngrx (± детали и поправка на то, что ngrx немного отличается по апи от редакса, а так почти то же самое), и попробуйте угадать, сколько раз приходилось писать несколько редьюсеров на 1 экшон? Верно — ноль :)
То есть в 99 случаях из ста мы получаем упрощение (экшон с редьюсером и пейлоадом действительно вырождаются в просто функцию с телом и аргументами), а в 1 оставшемся — ну можно написать как предполагает чистый redux way, предложенный подход полностью совместим. И даже если у вас не 99-1, а 90-10 или даже 80-20 соотношение, концептуально это ничего не меняет, и выгода от использования подобного решения будет.


ЗЫ: единственно что не совсем уверен как в rematch типизация работает, типы на вид адовенькие там

Если у вас настолько маленькие приложения, что «бойлерплейт» лишний

Любой бойлерплейт является лишним в любом приложении. И в большом приложении он легко может быть даже "более лишним", чем в маленьком. Потому что одно дело поддерживать 200 строк бойлерплейта, и совсем другое — 20000.

Сейчас MobX использую, пришёл к выводу, что это тот же Redux, только вид сбоку. Особой разницы между ними нет.
Если mobx использовать как redux через какой-нибудь mobx-state-tree, то разумеется никакой принципиальной разницы вы не заметите.
Сразу оговорюсь, к вебу отношения не имею, просто посматриваю на флаттер который вроде как немало у реакта перенял но при этом использует нормальный язык со статической типизацией компилирующийся в натив. Так вот, редакс туда тоже прикручивают, но для меня это выглядит перебором. Чем плохо получать данные из стримов (или observable из rx), и туда же их отправлять? Бойлерплейта немного, вьюшки перерендериваются при обновлении данных, можно иметь несколько не связанных хранилищ и вообще более гибко как то все выглядит, и масштабируемо. Особенно если придерживаться подхода с bloc который для флаттера предлагают. Правда есть одно но, я не только к вебу, к мобильной разработке тоже оооочень косвенное отношение имею, может не понимаю чего?

Все что вам нужно знать о Redux: вам он не нужен, если у вас маленькое и простое приложение и он вам не подойдет, если у вас большое и сложное приложение. В сложных случаях, для хранения данных лучше использовать графовые структуры, а в простых проще написать свой pub/sub на коленке: < 100 строк на все. Так что — нет.

Sign up to leave a comment.