Прочитать исходное состояние магазина в Redux Reducer

85

Начальное состояние в приложении Redux можно установить двумя способами:

Если вы передадите начальное состояние своему магазину, как вы прочитаете это состояние из магазина и сделаете его первым аргументом в ваших редукторах?

кантера
источник

Ответы:

185

TL; DR

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

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

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

Сначала рассмотрим случай, когда у вас есть единственный редуктор.
Скажите, что вы не используете 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не является неопределенным (это 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без initialState, это будет инициализировать 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' }

Теперь я указал в initialStateкачестве аргумента 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значение аргумента по умолчанию ES6), вы будете иметь приятное полезное поведение для комбинированных редукторов. Они предпочтут соответствующее значение в initialStateобъекте, который вы передаете createStore()функции, но если вы его не передали или если соответствующее поле не установлено, stateвместо него будет выбран аргумент по умолчанию, указанный редуктором.Этот подход работает хорошо, поскольку он обеспечивает как инициализацию, так и гидратацию существующих данных, но позволяет отдельным редукторам сбрасывать свое состояние, если их данные не были сохранены. Конечно, вы можете применять этот шаблон рекурсивно, как вы можете использовать combineReducers()на многих уровнях, или даже составлять редукторы вручную, вызывая редукторы и передавая им соответствующую часть дерева состояний.

Дан Абрамов
источник
3
Спасибо за прекрасную деталь - именно то, что я искал. Гибкость вашего API здесь просто великолепна. Часть, которую я не видел, заключается в том, что «дочерний» редуктор будет понимать, какая часть начального состояния принадлежит ему, на основе ключа, используемого в combineReducers. Еще раз большое спасибо.
кантера
Спасибо за этот ответ, Дэн. Если я правильно перевариваю ваш ответ, вы говорите, что лучше всего было бы установить начальное состояние через тип действия редуктора? Например, GET_INITIAL_STATE и вызвать отправку для этого типа действия, скажем, в обратном вызове componentDidMount?
Кон Антонакос
@ConAntonakos Нет, я не это имел в виду. Я имею в виду выбор начального состояния там, где вы определяете редуктор, например function counter(state = 0, action)или function visibleIds(state = [], action).
Дэн Абрамов
1
@DanAbramov: используя второй аргумент в createstore (), как я могу повторно гидратировать состояние при перезагрузке страницы SPA с последним сохраненным состоянием в redux вместо исходных значений. Думаю, я спрашиваю, как я могу кэшировать весь магазин и сохранить его при перезагрузке и передать его функции createstore.
jasan 02
1
@jasan: используйте localStorage. egghead.io/lessons/…
Дэн Абрамов
5

В двух словах: это Redux тот, кто передает начальное состояние редукторам, вам не нужно ничего делать.

Когда вы вызываете, createStore(reducer, [initialState])вы даете Redux знать, какое начальное состояние будет передано редуктору при первом действии.

Второй вариант, который вы упомянули, применяется только в том случае, если вы не передали начальное состояние при создании магазина. т.е.

function todoApp(state = initialState, action)

состояние будет инициализировано только в том случае, если Redux не передал состояние

Эмилио Родригес
источник
1
Спасибо за ответ - в случае композиции редуктора, если вы применили начальное состояние к хранилищу, как вы сообщите дочернему / вспомогательному редуктору, какой частью исходного дерева состояний он владеет? Например, если у вас есть menuState, как мне сказать редуктору меню прочитать значение state.menuиз магазина и использовать его в качестве начального состояния?
cantera
Спасибо, но мой вопрос, должно быть, непонятен. Я буду ждать ответа, прежде чем редактировать.
cantera
1

как вы читаете это состояние из магазина и делаете его первым аргументом в ваших редукторах?

combReducers () сделает всю работу за вас. Первый способ написать это бесполезно:

const rootReducer = combineReducers({ todos, users })

Но другой, эквивалентный, более понятен:

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}
Николас
источник
0

Надеюсь, это ответит на ваш запрос (который я понял как инициализацию редукторов при передаче intialState и возврате этого состояния)

Вот как мы это делаем (предупреждение: скопировано из кода Typescript).

Суть его в if(!state)тестировании в функции mainReducer (factory).

function getInitialState(): MainState {

     return {
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     }
}



const reducer = combineReducers(
    {
        main:     mainReducer( getInitialState() ),
        ...
    }
)



const mainReducer = ( initialState: MainState ): Reducer => {

    return ( state: MainState, action: Action ): MainState => {

        if ( !state ) {
            return initialState
        }

        console.log( 'Main reducer action: ', action ) 

        switch ( action.type ) {
            ....
        }
    }
}
Бруно Гридер
источник