RUVDS.com corporate blog
Website development
JavaScript
ReactJS
Comments 17
+2
1 — как-то очевидно
2 — Лучше бы звучало как «держите state максимально плоским»
Начиная с 3 и до конца, советы, прямо скажем, не про Redux.
+1
ага, 4 это про рефакторинг javascript кода в принципе. Используйте в своих проектах TypeScript и таких проблем никогда не будет))
+2
обман, везде обман…

сказали что 12 советов, но перечислили 11, так как 5й совет пропустили

troll mode activated: интересно, а если у вас на год взять сервер, то вместо обещанных 12 месяцев тоже получишь 11?
+2
Спасибо за комментарий. Ошибка с 12 советами идет из оригинала, там также пропущен 5 пункт. Статью и заголовок поправили.

marketing mode activated: Что касается оплаты сервера за год, то вы точно получите 12 месяцев, а в июне еще и скидку в 30%, плюс подарки от Ричарда Levelord'a.
+2

Я спустя 3-4 года возни с redux пришёл к таким выводам:


  • Action types не только не нужно хранить отдельно от action creators. Их вообще не нужно делать. Для чего они могут пригодится? Для того чтобы иметь в рамках одного типа несколько фабрик action-ов. На моей практике такой случай не наступил ни разу. Могу смело сказать, что в 99% это просто бойлерплейт. Мусор. Мусору нечего делать в кодовой базе. Хотите глоссарий экшнов — автоматизируйте это. Как связывать reducer-ы с action-ами? Очень просто импортировать actionCreator-ы там где у вас reducer-ы и цепляться к ранее установленным (автоматически) actionCreator.type. Эти не сильно простые шаги уменьшат боль при написании redux приложений раза в 2.


  • Держите в простых случаев reducer-ы там же где и actionCreator-ы. А сложных случаев избегайте (дробите). Может показаться странным, но это радикально уменьшает количество бойлерплейта и упрощает поддержку кода. При этом различные слои не путаются, просто лежат ближе. Недостатков пока не выявил. А вот разного рода селекторы бывает полезным вынести отдельно, т.к. их иногда может 3\4 от кода набраться и они не привязаны ни к каким экшн-тайпам.


  • Используйте proxy в reducer-ах. Это невероятным образом улучшает как кодовую базу, так и экономит ваше время. Мутабельный код с преимуществами иммутабельного. Лучшее что я видел для redux. Реализация довольно простая, да и есть готовые решения. После внедрения бизнес-логика перестаёт выглядеть как тихий ужас.


  • Забудте про switch-case. Ден привёл в качестве примера и понеслась. Преимуществ у них похоже никаких, но все их пишут и пишут. Куда удобнее держать привязку action-type -> handler(state, action, rootState)


  • 1 action = 1 handler. Не прогонять каждый action через все reducer-ы. Принудительно привязывать обработку задачи к одному handler-у. Делает код куда более предсказуемым. Это одна из важнейших вещей в redux-е. Как это обеспечить? Ну например держать путь к reducer-у в action.type. Хотя тут много вариаций.


  • Поменяйте сигнатуру каждого редьюсера с (state, action) на (state, action, rootState). Это позволит избежать тех хаков, когда вам для изменения под-части стора не хватает данных из другой ветви стора, и вы жутко извращаясь пихали эту логику прямо в actionCreator или выше. Это бессмысленный бред. При этом разумеется из одной ветки стора на другую повлиять нельзя. Цепляться не обязательно к rootState, тут надо смотреть на ваш IoC


  • Используйте нормализацию. Ну тут много про это писали. Это архиважно


  • Используйте memo, useAutoCallback, useAutoMemo, PureComponent и прочие примитивы с shallowComparison. Это азбука производительности вашего приложения. Очень помогут weakMemoize-ы разного рода. Селекторы, линзы и прочее. Обычно в серьёзном приложении таких утилит\помощников скапливается много.


0

Забыл добавить ещё 1 важный пункт. У меня всегда большая часть actionCreator-ов сводилась к:


  • получить список параметров
  • упаковать их в POJO дав правильные имена
  • приправить полем type

Это можно хорошо оптимизировать. Убрав метод вообще, сделав фабрику фабрик (creator of action creator). Можно даже валидацию добавить (актуально для не TS | Flow).


Уменьшает бойлерплейт значительно. Большинство таких actionCreator-ов легко помещаются в 1 строку. Учитывая отсутствие actionType-ов из других файлов, отдельных к ним импортов, и лишних экспортов… Можно приблизиться по уровню комфорта к норма...решениям на основе observable.

0
А можно пример «Забудте про switch-case» и «1 action = 1 handler»? Буду очень признателен.
-1
Забудте про switch-case

Смотрите. Что нам даёт конструкция switch-case? Разветвление. В случае reducer-ов разветвление по action.type. Чем хороша switch-case? В ней есть waterfall (когда 2 case имеют один кодовый блок). Чем плоха? Не самая удачная конструкция. Область видимости и вообще слишком разбухшая структура совсем не связанных между собой блоков кода. Чем плоха в случае reducer-ов? Тем что у нас итак reducer (если без proxy) из-за иммутабельности выглядят ужасно (сплошные портянки ..., какие-нибудь immutable callback-и, самописные методы и пр.), а тут ещё и вся лапша в одном месте на много-много строк. Зачастую на сотни строк. При этом мы не используем waterfall и по факту каждый case занимается своим и только своим случаем.


Что можно сделать взамен? Каждый case вынести в отдельный метод. Например:


switch(action.type) {
  case ACTION_1.type: return handler1(state, action);
  case ACTION_2.type: return handler2(state, action);
  default: return state;
}

const handler1 = (state, action) => { /* some logic */ };

Можно так:


const map = {
  [ACTION_1]: (state, action) => { /* logic */ },
  [ACTION_2]: importedHandler2,
  [ACTION_3]: (state, action) => {
    /* logic A */
    state = map[ACTION_2](state, action); // logic B
    /* logib C */
  }
};

export default (state, action) => { // reducer
  if (action.type in map) 
    return map[action.type](state, action);
  return state;
}

Можно и лучше. Бойлерплейт вынести в обёртки. Особенно если хочется подключить proxy.


1 action = 1 handler

Типовой каноничный redux-проект прогоняет ваш action через все ваши reducer-ы и все case-ы в switch-ах. Какие в этом преимущества? Оно одно: можно за-dispatch-ить 1 action, а изменить store в нескольких местах. Хорошо это или нет? Я считаю это зло, т.к. делает работу со store куда менее предсказуемой и прямолинейной. Особенно если изменение куска стора не гарантировано (скажем проверка по какому-нибудь флагу).


Как можно сделать иначе? Куча способов. Опишу самый простой (использую его везде сам). В action.type держать не просто уникальную строку, а строку с путём. PATH1/PATH2/PATH3/ACTION. И вместо combineReducer использовать сопоставление с сегментом этого пути. Тогда мы приходим к схеме 1 action = 1 блок кода который его обрабатывает. А reducer-ы куда в большей степени декларативны.


Какие недостатки у этого подхода?


  1. нельзя изменять стор сразу в 2 местах. Как решать? 2 action-а. На мой взгляд такой путь куда лучше подходит идее redux-а. Явное лучше неявного. Причём лучше вызывать 1 meta-actionCreator, который сделает dispatch нескольких. По правде говоря я с этим ограничением ещё не столкнулся. Мне кажется это хороший признак правильно построенной архитектуры.
  2. action-ы в большей степени привязаны к структуре store-а и reducer-ам. Это противоречит идеологии redux, когда action-ы не должны ничего знать о store и его структуре. Они ведь только намерения. Но тут я могу легко заметить, что это идеологическая картинка у нас и без того рушится в любом приложении, т.к. народ чуть ли не всю бизнес логику наворачивает в этих самых actionCreator-ах и вообще активно использует там getState. Да и actionTypes кладут рядом с reducer-ами (даже если в отдельный файл). Т.е. это яркий пример того, что на бумажке идея хороша, а проверку практикой не выдерживает. Т.е. то что идеологически обособленно почти гарантированно у всех оказывается связанным. Так что я тут просто предлагаю не притворяться, что эти 2 слоя с друг другом не связаны.

Вообще когда перестаёшь "притворяться" в redux радикально падает количество бойлерплейта. Вот первый же совет в этой статье идеологический. Если сделать в точности наоборот (а лучше вообще избавиться от action-type-ов) то в реальных проектах улучшается всё: разработка, поддержка, рефакторинг.


Я вначале старался слепо следовать идеологии redux во всех пунктах и постепенно наступил на все возможные грабли. Много переосмыслил, взвесил и построил по-другому. На данный момент ни о чём не жалею, обновлённая схема оказывается куда удобнее в разработке. Суть однонаправленного подхода остаётся прежней. Количество бойлерплейта падает в 3-10 раз (зависит от степени упоротости).

0
Спасибо больше за развёрнутый ответ.
Я оказывается использовал такое, с таким хэлпером:

export default function createReducer(initialState, handlers) {
    return (state = initialState, action) => {
        if (handlers.hasOwnProperty(action.type)) {
            return handlers[action.type](state, action)
        } else {
            return state
        }
    }
}


«Поменяйте сигнатуру каждого редьюсера с (state, action) на (state, action, rootState)» вот это ещё заинтриговало, не могу представить как туда ещё и стор весь передать.

Хотел ещё добавить про Action types, их удобно держать отдельно, если используется redux-saga
-1
«Поменяйте сигнатуру каждого редьюсера с (state, action) на (state, action, rootState)» вот это ещё заинтриговало, не могу представить как туда ещё и стор весь передать

На самом верху вам передаётся rootStore как первый аргумент. Если проконтролировать всё древо reducer-ов то вы можете не терять его. Можно скажем написать кастомный combineReducers с 3 параметрами (я так делал поначалу).


Касательно саг — не работал с ними, для меня это другой мир. Но кажется саги нужны там, где в более простых случаях пишут async-actionCreator-ы. А это по сути совсем другая сущность (во vuex её отделили даже). И там наверняка вы можете прибегнуть к тем же actionCreator.type (или .toString) без проблем.

0

Бегло посмотрел — выглядит интересно. Но надо пробовать в деле. Плюс я бы доработал напильником.

0
Не изменяйте структуры данных или типы в уже настроенных потоках данных приложений

[...]

Прошу вас этого не делать. Пожалуй, нечто подобное может позволить себе лишь тот, кто абсолютно точно знает о том, на какие части приложения это может повлиять.

Redux in a nutshell (редакс в двух словах). Эту проблему помогает решать TypeScript, но даже с ним рефакторинг модуля требует большой внимательности.

0

У MobX слишком много магии под капотом, далеко не всегда очевидно, что произойдёт, если сделать то или иное. Откат к предыдущим состояниям в MobX (undo, redo) — очень не тривиальная задача. Redux и MobX — оба не идеальные решения для управления состоянием.

0
Ну если откат к предыдущим значениям надо, то конечно MOBX не стоит юзать,
но вот мне что такое пока не разу не попалось, где прямо необходима эта иммутабельность.
А магия да, но с другой стороны где ее нету сейчас.
Я бы вообще не работал с реактом, если бы на вью такая же куча работы была.
Only those users with full accounts are able to leave comments. , please.