Когда приложение, использующее Redux, разрастается до достаточно больших размеров, количество состояний увеличивается многократно. Для разделения редьюсеров на логические единицы применяется подход комбинирования их с помощью combineReducers. Данное решение позволяет расширить store по «вертикали». Но бывают случаи, когда данного разделения может быть недостаточно. Например, один из уровней несет в себе составную логику, которую тоже было бы неплохо разделить (или как говорил один из известных людей: «Ухлубить!»). Но такого подхода нет в API Redux. И поиск решения данного вопроса так же ничего не дал (может плохо искал). Поэтому я разработал свой подход расширения по «горизонтали» Redux Store.
Хочу Вас ознакомить со своим проектом, который позволяет осуществить данный подход.
1) Сначала, на уровне редьюсера первого уровня, подключаем саму библиотеку:
2) Подключаем редьюсер для второго уровня:
3) Формируем начальные значения для первого уровня:
4) Создаем объект комбинаций:
где мы задаем название будущего раздела name prop, и для него — редьюсер этого уровня,а так же набор типов эшенов для триггеринга изменения стейта данного уровня.
5) Создаем функцию-обработчик текущего стейта:
6) Для обработки начальных значений всех уровней создаем комбинированный initial state.
7) В экспортной функции-редьюсере используем комбинированный initial state, а в ее теле строго до любого изменения состояния запускаем обработчик всех комбинаций, который меняет нужным образом текущее состояние, если текущий тип экшена совпал с заданными:
8) Модуль второго уровня оформляется по стандартной схеме, с учетом, что стейт в нем представлен в разрезе этого уровня:
но с одним отличием — должна быть проверка на undefined текущего action. Сделано для задание initial state при первом проходе в методе getInitialState.
Данный подход позволяет в рекурсивном режиме расширить до бесконечности текущий уровень и по «вертикали», за счет использованием в комбинациях более одного объекта:
и по «горизонтали», за счет использования описанного выше подхода на каждом из 2+ уровней.
> Исходники
UPD:
Полный рефакторинг кода, большое спасибо dagen, за указание на проблему мутабельности. Теперь немного поменялся принцип использования, смотрите п.7 и п.4 — набор экшенов теперь отсутствует за ненадобностью, но комбинации пока оставил как объект для возможного дальнейшего расширения функционала. Замечу, что данный подход я использовал со связкой с PolymerJS, а потом с VueJS, и использовал для интеграции с Redux библиотеки polymer-redux и vuedeux соответственно. И так как там бинд на конкретные свойства стейта идут по пути, то меня и миновала проблема мутаций, так как было необязательно мутировоать рутовый стейт при мутировании одного из поддеревьев.
Хочу Вас ознакомить со своим проектом, который позволяет осуществить данный подход.
Использование
1) Сначала, на уровне редьюсера первого уровня, подключаем саму библиотеку:
import {stateCombine, runCombine, getInitialState} from "redux-combine-deep-props";
2) Подключаем редьюсер для второго уровня:
import level2Module from "./reducer-level-2";
3) Формируем начальные значения для первого уровня:
let initialState = {
propLevel1: ...,
...
propLevelN: ...
};
4) Создаем объект комбинаций:
let combinations = {
<name prop>: {
module: level2Module
}
};
где мы задаем название будущего раздела name prop, и для него — редьюсер этого уровня,
5) Создаем функцию-обработчик текущего стейта:
let combineDeepProp = stateCombine(combinations);
let combine = runCombine(combinations, combineDeepProp);
6) Для обработки начальных значений всех уровней создаем комбинированный initial state.
const combineInitialState = getInitialState(combinations, initialState);
7) В экспортной функции-редьюсере используем комбинированный initial state, а в ее теле строго до любого изменения состояния запускаем обработчик всех комбинаций, который меняет нужным образом текущее состояние, если текущий тип экшена совпал с заданными:
export default function level1Module (state = combineInitialState, action) {
...
let newState = combine(state, action);
...
switch (action.type) {
case "....":
newState = {
...newState,
...
};
break;
...
};
...
return newState;
};
8) Модуль второго уровня оформляется по стандартной схеме, с учетом, что стейт в нем представлен в разрезе этого уровня:
let initialState = {
...
};
export default function search(state = initialState, action) {
...
switch (action && action.type) {
...
};
};
но с одним отличием — должна быть проверка на undefined текущего action. Сделано для задание initial state при первом проходе в методе getInitialState.
Заключение
Данный подход позволяет в рекурсивном режиме расширить до бесконечности текущий уровень и по «вертикали», за счет использованием в комбинациях более одного объекта:
let combinations = {
<name prop1>: {
module: level2Module1
},
...
<name propN>: {
module: level2ModuleN
}
};
и по «горизонтали», за счет использования описанного выше подхода на каждом из 2+ уровней.
> Исходники
UPD:
Полный рефакторинг кода, большое спасибо dagen, за указание на проблему мутабельности. Теперь немного поменялся принцип использования, смотрите п.7 и п.4 — набор экшенов теперь отсутствует за ненадобностью, но комбинации пока оставил как объект для возможного дальнейшего расширения функционала. Замечу, что данный подход я использовал со связкой с PolymerJS, а потом с VueJS, и использовал для интеграции с Redux библиотеки polymer-redux и vuedeux соответственно. И так как там бинд на конкретные свойства стейта идут по пути, то меня и миновала проблема мутаций, так как было необязательно мутировоать рутовый стейт при мутировании одного из поддеревьев.