Leia o estado inicial da loja no redutor redutor

85

O estado inicial em um aplicativo Redux pode ser definido de duas maneiras:

Se você passar o estado inicial para sua loja, como você lê esse estado da loja e o torna o primeiro argumento em seus redutores?

Cantera
fonte

Respostas:

185

TL; DR

Sem combineReducers()ou código manual semelhante, initialStatesempre vence state = ...no redutor porque o statepassado para o redutor é initialState e não é undefined , portanto, a sintaxe do argumento ES6 não é aplicada neste caso.

Com combineReducers()o comportamento é mais matizado. Os redutores cujo estado é especificado em initialStatereceberão isso state. Outros redutores receberão undefined e, por causa disso , voltarão ao state = ...argumento padrão que especificarem.

Em geral, initialStatevence o estado especificado pelo redutor. Isso permite que os redutores especifiquem dados iniciais que façam sentido para eles como argumentos padrão, mas também permite carregar os dados existentes (total ou parcialmente) quando você está hidratando o armazenamento a partir de algum armazenamento persistente ou do servidor.

Primeiro, vamos considerar um caso em que você tem um único redutor.
Diga que você não usa combineReducers().

Então, seu redutor pode ter a seguinte aparência:

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

Agora, digamos que você crie uma loja com ele.

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

O estado inicial é zero. Por quê? Porque o segundo argumento createStoreera undefined. Este é statepassado ao seu redutor pela primeira vez. Quando o Redux é inicializado, ele despacha uma ação “fictícia” para preencher o estado. Portanto, seu counterredutor foi chamado com stateigual a undefined. Este é exatamente o caso que “ativa” o argumento padrão. Portanto, stateagora está de 0acordo com o statevalor padrão ( state = 0). Este estado ( 0) será retornado.

Vamos considerar um cenário diferente:

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

Por que é 42, e não 0, desta vez? Porque createStorefoi chamado com 42o segundo argumento. Este argumento passa a ser statepassado ao seu redutor junto com a ação fictícia. Desta vez, statenão é indefinido (é 42!), Então a sintaxe de argumento padrão do ES6 não tem efeito. O stateé 42e 42é retornado do redutor.


Agora vamos considerar um caso em que você usa combineReducers().
Você tem dois redutores:

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

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

O redutor gerado por se combineReducers({ a, b })parece com este:

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

Se chamarmos createStoresem o initialState, ele inicializará o statepara {}. Portanto, state.ae state.bserá undefinedquando ele ligar ae bredutores. Ambos ae os bredutores receberão undefinedcomo seus state argumentos e, se especificarem statevalores padrão , eles serão retornados. É assim que o redutor combinado retorna um { a: 'lol', b: 'wat' }objeto de estado na primeira chamada.

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

Vamos considerar um cenário diferente:

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

Agora eu especifiquei o initialStatecomo o argumento para createStore(). O estado retornado do redutor combinado combina o estado inicial que eu especifiquei para o aredutor com o 'wat'argumento padrão especificado que o bpróprio redutor escolheu.

Vamos lembrar o que o redutor combinado faz:

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

Neste caso, statefoi especificado para não retornar {}. Era um objeto com acampo igual a 'horse', mas sem o bcampo. É por isso que o aredutor recebeu 'horse'como seu statee alegremente o devolveu, mas o bredutor recebeu undefinedcomo seu statee, portanto, retornou sua ideia de default state(em nosso exemplo, 'wat'). É assim que recebemos { a: 'horse', b: 'wat' }em troca.


Para resumir, se você seguir as convenções do Redux e retornar o estado inicial dos redutores quando eles forem chamados com undefinedo stateargumento (a maneira mais fácil de implementar isso é especificar o statevalor do argumento padrão ES6), você terá um bom comportamento útil para redutores combinados. Eles irão preferir o valor correspondente no initialStateobjeto que você passar para a createStore()função, mas se você não passou nenhum, ou se o campo correspondente não foi definido, o padrão.state argumento especificado pelo redutor é escolhido. Esta abordagem funciona bem porque fornece inicialização e hidratação dos dados existentes, mas permite que redutores individuais redefinam seu estado se seus dados não foram preservados. Claro, você pode aplicar este padrão recursivamente, pois você pode usarcombineReducers() em muitos níveis, ou mesmo componha redutores manualmente chamando redutores e dando a eles a parte relevante da árvore de estado.

Dan Abramov
fonte
3
Obrigado pelo grande detalhe - exatamente o que eu estava procurando. A flexibilidade de sua API aqui é brilhante. A parte que eu não estava vendo é que o redutor "filho" entenderá qual parte do estado inicial pertence a ele com base na chave usada em combineReducers. Muito obrigado novamente.
cantera
Obrigado por esta resposta, Dan. Se estou digerindo sua resposta corretamente, você está dizendo que seria melhor definir um estado inicial por meio de um tipo de ação do redutor? Por exemplo, GET_INITIAL_STATE e chamar um despacho para esse tipo de ação em, digamos, um retorno de chamada componentDidMount?
Con Antonakos de
@ConAntonakos Não, eu não quis dizer isso. Quero dizer, escolher o estado inicial exatamente onde você define o redutor, por exemplo, function counter(state = 0, action)ou function visibleIds(state = [], action).
Dan Abramov,
1
@DanAbramov: usando o segundo argumento em createstore (), como posso reidratar o estado na recarga da página SPA com o último estado armazenado em redux em vez dos valores iniciais. Acho que estou perguntando como posso armazenar em cache a loja inteira e preservá-lo ao recarregar e passá-lo para a função de createstore.
jasan
1
@jasan: Use localStorage. egghead.io/lessons/…
Dan Abramov
5

Resumindo: é o Redux quem passa o estado inicial para os redutores, você não precisa fazer nada.

Ao ligar, createStore(reducer, [initialState])você está informando ao Redux qual é o estado inicial a ser passado para o redutor quando a primeira ação ocorrer.

A segunda opção que você mencionou, aplica-se apenas no caso de você não passar de um estado inicial ao criar a loja. ie

function todoApp(state = initialState, action)

estado só será inicializado se não houver estado passado por Redux

Emilio Rodriguez
fonte
1
Obrigado por responder - no caso da composição do redutor, se você aplicou o estado inicial à loja, como você diz ao redutor filho / sub que parte da árvore de estado inicial ele possui? Por exemplo, se você tiver um menuState, como faço para dizer ao redutor de menu para ler o valor de state.menuda loja e usá-lo como seu estado inicial?
cantera
Obrigado, mas minha pergunta não deve ser clara. Vou esperar para ver se outros respondem antes de editar.
cantera
1

como você lê esse estado da loja e o torna o primeiro argumento em seus redutores?

combineReducers () faz o trabalho por você. A primeira maneira de escrever não é realmente útil:

const rootReducer = combineReducers({ todos, users })

Mas o outro, que é equivalente, é mais claro:

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}
Nicolas
fonte
0

Espero que isso responda à sua solicitação (que entendi como redutores de inicialização ao passar intialState e retornar esse estado)

É assim que fazemos (aviso: copiado do código Typescript).

A essência disso é o if(!state)teste na função mainReducer (fábrica)

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 ) {
            ....
        }
    }
}
Bruno Grieder
fonte