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

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

Инициализация состояния

Существует два основных способа инициализировать состояние вашего приложения. Метод createStore может принимать необязательное значение preloadedState в качестве второго аргумента. Редюсеры также могут задавать начальное значение, проверяя, если входящий аргумент state равен undefined, и возвращая значение, которое они хотят использовать по умолчанию. Это можно сделать либо с помощью явной проверки внутри редюсера, либо используя синтаксис значения аргумента по умолчанию: function myReducer(state = someDefaultValue, action).

Не всегда сразу понятно, как эти два подхода взаимодействуют. К счастью, процесс следует предсказуемым правилам. Вот как всё работает вместе.

Резюме

Без использования combineReducers() или аналогичного ручного кода preloadedState всегда имеет приоритет над state = ... в редюсере, потому что state, передаваемый в редюсер, является preloadedState и не является undefined, поэтому синтаксис аргумента по умолчанию не применяется.

С combineReducers() поведение более нюансированное. Те редюсеры, состояние которых указано в preloadedState, получат это состояние. Остальные редюсеры получат undefined и поэтому будут использовать значение по умолчанию state = ..., заданное в их аргументах.

В общем случае preloadedState имеет приоритет над состоянием, указанным в редюсере. Это позволяет редюсерам задавать начальные данные, которые для них имеют смысл, как аргументы по умолчанию, но также позволяет загружать существующие данные (полностью или частично) при гидрации хранилища из постоянного хранилища или с сервера.

Примечание: Редюсеры, начальное состояние которых заполняется с помощью preloadedState, всё равно должны предоставлять значение по умолчанию, чтобы обрабатывать случай, когда им передаётся state равный undefined. Все редюсеры при инициализации получают undefined, поэтому они должны быть написаны так, чтобы при получении undefined возвращать какое-либо значение. Это может быть любое значение, отличное от undefined; нет необходимости дублировать здесь раздел preloadedState в качестве значения по умолчанию.

Подробно

Один простой редюсер

Сначала рассмотрим случай, когда у вас один редюсер. Допустим, вы не используете combineReducers().

Тогда ваш редюсер может выглядеть так:

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

Теперь предположим, что вы создаёте хранилище с его помощью.

import { createStore } from 'redux'
const store = createStore(counter)
console.log(store.getState()) // 0

Начальное состояние равно нулю. Почему? Потому что второй аргумент в createStore был undefined. Это state, который передаётся в ваш редюсер при первом вызове. При инициализации Redux диспатчит "фиктивное" действие для заполнения состояния. Таким образом, ваш редюсер counter был вызван с state, равным undefined. Это именно тот случай, который "активирует" аргумент по умолчанию. Следовательно, state теперь равен 0 в соответствии со значением по умолчанию для state (state = 0). Это состояние (0) и будет возвращено.

Рассмотрим другой сценарий:

import { createStore } from 'redux'
const store = createStore(counter, 42)
console.log(store.getState()) // 42

Почему теперь 42, а не 0? Потому что createStore был вызван с 42 в качестве второго аргумента. Этот аргумент становится state, который передаётся в редюсер вместе с фиктивным действием. В этот раз state не undefined (это 42!), поэтому синтаксис аргумента по умолчанию не срабатывает. state равен 42, и из редюсера возвращается 42.

Комбинированные редюсеры

Теперь рассмотрим случай, когда вы используете combineReducers(). У вас есть два редюсера:

function a(state = 'lol', action) {
return state
}

function b(state = 'wat', action) {
return state
}

Редюсер, созданный с помощью combineReducers({ a, b }), выглядит так:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

Если мы вызовем createStore без preloadedState, то state будет инициализирован как {}. Следовательно, к моменту вызова редюсеров a и b, state.a и state.b будут undefined. Оба редюсера a и b получат undefined в качестве своих аргументов state, и если они задают значения по умолчанию для state, то будут возвращены именно они. Так комбинированный редюсер возвращает объект состояния { a: 'lol', b: 'wat' } при первом вызове.

import { createStore } from 'redux'
const store = createStore(combined)
console.log(store.getState()) // { a: 'lol', b: 'wat' }

Рассмотрим другой сценарий:

import { createStore } from 'redux'
const store = createStore(combined, { a: 'horse' })
console.log(store.getState()) // { a: 'horse', b: 'wat' }

Теперь я указал preloadedState в качестве аргумента createStore(). Состояние, возвращаемое комбинированным редюсером, объединяет начальное состояние, которое я указал для редюсера a, со значением по умолчанию 'wat', которое выбрал для себя редюсер b.

Вспомним, что делает комбинированный редюсер:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
return {
a: a(state.a, action),
b: b(state.b, action)
}
}

В этом случае state был указан, поэтому он не использовал значение по умолчанию {}. Это был объект с полем a равным 'horse', но без поля b. Вот почему редюсер a получил 'horse' в качестве своего state и вернул его, а редюсер b получил undefined в качестве своего state и поэтому вернул своё представление о дефолтном state (в нашем примере 'wat'). Таким образом, мы получаем { a: 'horse', b: 'wat' }.

Итог

Подводя итог: если вы следуете соглашениям Redux и возвращаете начальное состояние из редюсеров, когда они вызываются с undefined в качестве аргумента state (проще всего это сделать, указав значение аргумента state по умолчанию), вы получите удобное поведение для комбинированных редюсеров. Они будут отдавать предпочтение соответствующему значению в объекте preloadedState, который вы передаёте в функцию createStore(), но если вы ничего не передали или соответствующее поле не задано, будет выбрано значение по умолчанию для аргумента state, указанное в редюсере. Этот подход хорошо работает, потому что обеспечивает как инициализацию, так и гидрацию существующих данных, но позволяет отдельным редюсерам сбрасывать своё состояние, если их данные не были сохранены. Конечно, вы можете применять этот паттерн рекурсивно, так как combineReducers() можно использовать на нескольких уровнях или даже составлять редюсеры вручную, вызывая их и передавая соответствующую часть дерева состояния.