Инициализация состояния
Существуют два основных способа инициализации состояния вашего приложения. Метод createStore может принимать необязательное значение preloadedState в качестве второго аргумента. Редьюсеры также могут указать начальное значение, проверяя входной аргумент состояния на undefined, и возвращая значение, которое они хотели бы использовать по умолчанию. Это можно сделать с помощью явной проверки внутри редьюсера или с помощью синтаксиса аргументов по умолчанию ES6: function myReducer(state = someDefaultValue, action).
Не всегда сразу понятно, как эти два подхода взаимодействуют. К счастью, процесс подчиняется некоторым предсказуемым правилам. Вот как все части объединяются.
Обзор
Без combineReducers() или аналогичного ручного кода, preloadedState всегда имеет приоритет над state = ... в редьюсере, потому что state, переданный редьюсеру, является preloadedState и не является undefined, поэтому синтаксис аргументов ES6 не применяется.
При использовании 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';
let 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';
let store = createStore(counter, 42);
console.log(store.getState()); // 42
Почему это 42, а не 0, на этот раз? Потому что createStore был вызван со значением 42 в качестве второго аргумента. Этот аргумент становится state, переданным вашему редьюсеру вместе с фиктивным действием. На этот раз state не является undefined (он равен 42!), поэтому синтаксис аргументов по умолчанию ES6 не имеет эффекта. 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 значением {}. Поэтому state.a и state.b будут undefined к моменту вызова редьюсеров a и b. Оба редьюсера a и b получат undefined в качестве своих аргументов state, и если они зададут значения по умолчанию state, эти значения будут возвращены. Вот как объединённый редьюсер возвращает объект состояния { a: 'lol', b: 'wat' } при первом вызове.
import { createStore } from 'redux';
let store = createStore(combined);
console.log(store.getState()); // { a: 'lol', b: 'wat' }
Рассмотрим другой сценарий:
import { createStore } from 'redux';
let 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 (самый простой способ сделать это — указать значение аргумента по умолчанию ES6), вы получите хорошее и полезное поведение для объединенных редьюсеров. Они будут отдавать предпочтение соответствующему значению в объекте preloadedState, который вы передаете функции createStore(), но если вы ничего не передали или соответствующее поле не установлено, вместо этого будет выбрано значение по умолчанию state, заданное редьюсером. Этот подход хорошо работает, потому что он обеспечивает как инициализацию, так и гидратацию существующих данных, но позволяет отдельным редьюсерам сбрасывать свое состояние, если их данные не были сохранены. Конечно, вы можете применять этот шаблон рекурсивно, так как вы можете использовать combineReducers() на многих уровнях или даже составлять редьюсеры вручную, вызывая редьюсеры и предоставляя им соответствующую часть дерева состояния.
© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/recipes/reducers/InitializingState.html