Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →
Использование combineReducers
Основные концепции
Наиболее распространённая структура состояния в Redux-приложении — это простой JavaScript-объект, содержащий "срезы" (slices) предметно-ориентированных данных на уровне верхних ключей. Аналогично, стандартный подход к написанию логики редукторов для такой структуры — создание функций-"срезов" (slice reducers) с одинаковой сигнатурой (state, action), где каждая отвечает за обновления своего участка состояния. Несколько срезовых редукторов могут реагировать на одно действие, независимо обновлять свои участки, после чего обновлённые срезы объединяются в новый объект состояния.
Поскольку этот шаблон чрезвычайно распространён, Redux предоставляет утилиту combineReducers для его реализации. Это пример редуктора высшего порядка, который принимает объект с функциями-срезами и возвращает новый редуктор.
При использовании combineReducers важно понимать несколько ключевых моментов:
-
Прежде всего,
combineReducers— вспомогательная функция для упрощения самого частого сценария при работе с редукторами Redux. Вы не обязаны использовать её, и она не покрывает все возможные ситуации. Вполне возможно писать логику редукторов без неё, и часто требуется кастомная логика для случаев, которыеcombineReducerне обрабатывает. (Примеры и рекомендации см. в BeyondcombineReducers.) -
Хотя сам Redux не навязывает структуру состояния,
combineReducersприменяет несколько правил, чтобы предотвратить распространённые ошибки. (Подробности см. вcombineReducers.) -
Частый вопрос: вызывает ли Redux "все редукторы" при диспетчеризации действия? Поскольку корневой редуктор только один, формально — "нет". Однако
combineReducersреализован так, что фактически делает это. Для формирования нового дерева состоянияcombineReducersвызовет каждый срезовый редуктор, передав ему текущий срез состояния и действие, давая возможность обновить свой участок. Таким образом, при использованииcombineReducersдействительно "вызываются все редукторы" (точнее, все обёрнутые срезовые редукторы). -
Её можно использовать на любом уровне структуры редукторов, не только для создания корневого. Часто встречаются несколько комбинированных редукторов в разных местах, которые затем объединяются в корневой.
Определение структуры состояния
Есть два способа задать начальную структуру и содержимое состояния хранилища. Первый: функция createStore принимает preloadedState как второй аргумент. Это в основном для инициализации состояния извне (например, из localStorage). Второй способ: корневой редуктор возвращает начальное состояние, когда аргумент state равен undefined. Оба подхода подробно рассмотрены в Инициализация состояния, но при использовании combineReducers есть дополнительные нюансы.
combineReducers принимает объект с функциями-срезами и создаёт функцию, возвращающую объект состояния с теми же ключами. Это означает: если createStore не получает предзагруженное состояние, имена ключей во входном объекте редьюсеров срезов будут определять ключи в выходном объекте состояния. Соответствие имён не всегда очевидно, особенно при использовании дефолтного экспорта модулей и сокращённой записи объектов.
Пример того, как сокращённая запись объектов в combineReducers определяет структуру состояния:
// reducers.js
export default theDefaultReducer = (state = 0, action) => state
export const firstNamedReducer = (state = 1, action) => state
export const secondNamedReducer = (state = 2, action) => state
// rootReducer.js
import { combineReducers, createStore } from 'redux'
import theDefaultReducer, {
firstNamedReducer,
secondNamedReducer
} from './reducers'
// Use object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
})
const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
Обратите внимание: из-за сокращённой записи ключи в результирующем состоянии совпадают с именами переменных из импортов. Такое поведение не всегда желательно и часто вызывает путаницу у тех, кто не знаком с современным синтаксисом JS.
Кроме того, полученные имена выглядят немного странно. Обычно не рекомендуется включать слова вроде "reducer" в названия ключей состояния — ключи должны просто отражать предметную область или тип хранимых данных. Это означает, что нам следует либо явно указать имена ключей в объекте срезов редьюсеров для определения ключей в выходном объекте состояния, либо тщательно переименовать переменные для импортируемых срезов редьюсеров при использовании сокращённого синтаксиса объектных литералов.
Более правильное использование может выглядеть так:
import { combineReducers, createStore } from 'redux'
// Rename the default import to whatever name we want. We can also rename a named import.
import defaultState, {
firstNamedReducer,
secondNamedReducer as secondState
} from './reducers'
const rootReducer = combineReducers({
defaultState, // key name same as the carefully renamed default export
firstState: firstNamedReducer, // specific key name instead of the variable name
secondState // key name same as the carefully renamed named export
})
const reducerInitializedStore = createStore(rootReducer)
console.log(reducerInitializedStore.getState())
// {defaultState : 0, firstState : 1, secondState : 2}
Эта структура состояния лучше отражает данные, поскольку мы позаботились о явном указании ключей при передаче в combineReducers.