Перейти к основному содержимому
Неофициальный Бета-перевод

Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →

Использование combineReducers

Основные концепции

Наиболее распространённая структура состояния в Redux-приложении — это простой JavaScript-объект, содержащий "срезы" (slices) предметно-ориентированных данных на уровне верхних ключей. Аналогично, стандартный подход к написанию логики редукторов для такой структуры — создание функций-"срезов" (slice reducers) с одинаковой сигнатурой (state, action), где каждая отвечает за обновления своего участка состояния. Несколько срезовых редукторов могут реагировать на одно действие, независимо обновлять свои участки, после чего обновлённые срезы объединяются в новый объект состояния.

Поскольку этот шаблон чрезвычайно распространён, Redux предоставляет утилиту combineReducers для его реализации. Это пример редуктора высшего порядка, который принимает объект с функциями-срезами и возвращает новый редуктор.

При использовании combineReducers важно понимать несколько ключевых моментов:

  • Прежде всего, combineReducersвспомогательная функция для упрощения самого частого сценария при работе с редукторами Redux. Вы не обязаны использовать её, и она не покрывает все возможные ситуации. Вполне возможно писать логику редукторов без неё, и часто требуется кастомная логика для случаев, которые combineReducer не обрабатывает. (Примеры и рекомендации см. в Beyond combineReducers.)

  • Хотя сам 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.