Мы называем состоянием все, что находится в хранилище. При этом не все состояния одинаково полезны. Документация Redux предлагает такую классификацию:
- Domain data — это данные, которые нужно отображать, использовать и модифицировать (например, загруженный с сервера список пользователей)
- App state — данные, которые определяют поведение приложения (например, открытый URL)
- UI state — данные, которые определяют внешний вид (например, вывод списка в виде плиток)
Хранилище — это ядро приложения, поэтому данные внутри него должны описываться в терминах domainData и appState, а не как дерево компонентов UI.
Например, способ формирования состояния state.leftPane.todoList.todos — плохая идея. Очень редко дерево компонентов отражается напрямую на структуру состояния, и это нормально. Представление зависит от данных, а не данные от представления.
Типичная структура состояния выглядит так:
{
domainData1 : {}, // todos
domainData2 : {}, // comments
appState1 : {},
appState2 : {},
uiState1 : {},
uiState2 : {},
}
Как уже говорилось в курсе JS: React, структура состояния должна напоминать базу данных. Все максимально плоско и нормализованно:
{
todos: [
{ id: 1, name: 'why?' },
{ id: 3, name: 'who?' },
],
comments: [
{ id: 23, todoId: 3, text: 'great!' },
],
}
С такой структурой намного проще писать реакции на действия, добавлять новые данные и удалять старые. Вложенность небольшая, поэтому все просто. Но появляется другая проблема — чем больше сущностей, тем тяжелее становится редьюсер. Постепенно он превращается в огромный кусок кода, который делает все.
Чтобы решить эту проблему, можно использовать механизм, встроенный в Redux. Он позволяет создавать множественные редьюсеры и комбинировать их друг с другом. Работает это так:
- Для каждого свойства верхнего уровня пишется свой редьюсер
- С помощью функции
combineReducers()
все редьюсеры объединяются в корневой редьюсер (root) - Корневой редьюсер используется для создания хранилища
В итоге действия попадают во все редьюсеры, собранные внутри combineReducers()
:
import { combineReducers, createStore } from 'redux';
const todosReducer = (state = [], action) => {
// Сюда попадут данные из todos
};
const commentsReducer = (state = [], action) => {
// Сюда попадут данные из comments
};
const rootReducer = combineReducers({
todos: todosReducer,
comments: commentsReducer,
});
const store = createStore(rootReducer);
// Если назвать редьюсеры как свойства в состоянии, то код сократится:
// const todos = (state = [], action) => { ... };
// const comments = (state = [], action) => { ... };
// const rootReducer = combineReducers({ todos, comments });
В каждый редьюсер приходит state, но это не все состояние хранилища, а только та часть, которая лежит в соответствующем свойстве. В примере выше в редьюсер todosReducer
будет передаваться состояние из свойства todos
, а в commentsReducer
из comments
. Эта привязка происходит через объект, который передается в combineReducers()
:
combineReducers({
todos: todosReducer, // редьюсер привязывается к свойству todos глобального состояния
comments: commentsReducer, // редьюсер привязывается к свойству comments глобального состояния
});
Схематично комбинацию редьюсеров можно обозначить так:
А еще редьюсеры могут быть даже вложенными. Для этого не нужны никакие специальные средства — это обычные функции, принимающие одни данные и возвращающие другие данные.
С таким подходом появляется одна особенность, которая вначале может испугать. Каждый редьюсер имеет доступ только к своей части состояния, поэтому действия, порождающие изменения сразу в нескольких местах, будут повторяться в разных редьюсерах:
const todos = (state = {}, action) => {
switch (action.type) {
case 'TODO_REMOVE':
// ...
}
};
const comments = (state = {}, action) => {
switch (action.type) {
// При удалении ToDo нужно удалить все его комментарии
case 'TODO_REMOVE':
// ...
}
};
Правильный подход в этом случае — повторять часть case
в нужных редьюсерах.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.