Redux: Eu forneço o initialState em createStore e, em seguida, combineReducers mostro que um dos meus redutores retornou indefinido durante a inicialização

Criado em 23 ago. 2017  ·  27Comentários  ·  Fonte: reduxjs/redux

em redutores:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

em blogTypeVisibilityFilter:

const blogTypeVisibilityFilter = (state, action)=>{
  switch (action.type) {
    case 'BLOG_TYPE_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default blogTypeVisibilityFilter;

em blogs:

const blogs = (state,action)=>{
  return state
}

em createStore:

const initialState = {
  blogTypeVisibilityFilter:'SHOW_ALL_BLOG',
  blogs:data.data,
}

const store = createStore(reducer,initialState,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

e então mostra errado:

O redutor "blogTypeVisibilityFilter" retornou indefinido durante a inicialização. Se o estado passado para o redutor for indefinido, você deve retornar explicitamente ao estado inicial. O estado inicial não pode ser indefinido. Se você não quiser definir um valor para este redutor, você pode usar nulo em vez de indefinido.

mas quando eu apenas mudo

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

para

const todoBlog = (state={},action)=>{
  return{
    blogTypeVisibilityFilter:blogTypeVisibilityFilter(state.blogTypeVisibilityFilter,action),
    blogs:blogs(state.blogs,action)
  }
}

em redutores funciona bem e sem erros

porque eu uso combineReducers dá errado?

Comentários muito úteis

Este é um rastreador de bug, não um sistema de suporte. Para questões de uso, use Stack Overflow ou Reactiflux. Obrigado!

Todos 27 comentários

Este é um rastreador de bug, não um sistema de suporte. Para questões de uso, use Stack Overflow ou Reactiflux. Obrigado!

Eu tenho o mesmo problema.

Degraus:

  1. Formato de loja simples de imagem { foo, bar } .
  2. Crie um redutor simples como simpleReducer = state => state .
  3. Combine redutores em reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }) .
  4. Crie um estado com valor inicial com createState(reducers, { foo: Obj1, bar: Obj2 }) .
  5. Obtenha o erro Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

A razão é assertReducerShape que executa todos os redutores com estado indefinido para obter as partes do estado inicial. Meu redutor simples retorna seu argumento, que é indefinido nesse caso. Por que Obj1 de createState chamada não é considerada como estado inicial de foo-part?

@timdorr você pode reabrir o problema?

A solução alternativa é fazer simpleReducer = (state = null) => state . Mas tudo bem?

Com combineReducers , espera-se que cada redutor de fatia "possua" sua fatia de estado. Isso significa fornecer um valor inicial caso a fatia atual seja indefinida, e combineReducers indicará se você não estiver fazendo isso.

'Estado inicial' tem vários significados. Eles são o valor de retorno padrão de redutor e o segundo argumento de createStore. Acho que há um problema. Você pode explicar por que combineReducers verifica os redutores para initialState e createState não?

@Vittly : combineReducers é deliberadamente opinativo, enquanto createStore não é. createStore simplesmente chama qualquer função de redutor de raiz que você atribuiu a ele e salva o resultado. combineReducers assume que, se você o estiver usando, está acreditando em um conjunto específico de suposições sobre como o estado deve ser organizado e como os redutores combinados devem se comportar.

Você pode querer ler os números 191 e 1189, que detalham a história e os detalhes de por que combineReducers tem opiniões e quais opiniões tem.

Portanto, o problema é (em breve) "se você combinar redutores, você também divide sua árvore de loja e pode perder algo ou pré-carregar deliberadamente apenas parte da loja no segundo argumento do createStore. Para tornar a árvore de loja mais consistente, use assertReducerShape". Ok, obrigado pelos refs

Mesmo problema aqui.
Então eu uso createStore e passo um estado inicial. Mas no arquivo reduct.js , a função assertReducerShape redux verificará meus redutores ao passar por um estado undefined .

IMHO este é um bug.

@JoseFMP : Eu efetivamente garantiria que tudo o que você está vendo _não_ é um bug. No entanto, se você puder montar um repo ou CodeSandbox que demonstre o problema, podemos dar uma olhada.

Certo. Registrei um problema em questão no Stackoverflow:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

O problema é bastante simples. Então, se eu definir um estado inicial ao criar a loja ... por que redux quer avaliar o redutor com um estado undefined ? Isso faz com que todos os redutores retornem um novo estado undefined .

Mas não faz sentido, já que passamos por um estado inicial.
Então, isso está falhando:

import { combineReducers, createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customData = (customData: any, action: any): any =>  {
        return customData;
}
const reducers = combineReducers({config: configReducer, customData: customDataReducer})

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP :

Ah, acho que sei qual é o problema. Isso é específico para o funcionamento de combineReducers() .

combineReducers espera que todos os "redutores de fatias" que você fornecer sigam algumas regras. Em particular, ele espera que _se_ seu redutor for chamado com state === undefined , ele retornará um valor padrão adequado. Para verificar isso, combineReducers() irá realmente chamar seu redutor com (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) para ver se ele retorna um valor indefinido ou "real".

Atualmente, seus redutores não fazem nada, exceto retornar o que foi passado. Isso significa que se state for indefinido, eles _retornarão_ indefinido. Portanto, combineReducers() está dizendo que você está quebrando o resultado esperado.

Apenas altere as declarações para algo como configReducer = (config = {}, action) , etc, e isso irá corrigir os avisos.

Novamente, para ser claro: isto _não_ é um bug. Esta é a validação do comportamento.

Hej @markerikson obrigado pela sua resposta.
Alguns comentários:

1) Não, NÃO é específico de combineReducers . Se eu não usar combineReducers e implementar meu próprio redutor de raiz, o mesmo acontece.

2) A inconsistência aqui é que, na documentação do Redux é mencionado que se o redutor for chamado com um status desconhecido ou inesperado, ele não deve modificar o status e retornar o valor que veio como argumento. Ou seja ... se receber undefined , deverá retornar undefined . Mas outra regra diz que o redutor nunca deve retornar undefined portanto, essas duas regras, como estão atualmente na documentação do redux, são inconsistentes. E inconsistente com a implementação.

@JoseFMP : como eu disse anteriormente, se você puder fornecer um CodeSandbox que demonstre especificamente o problema, com comentários apontando exatamente o que você espera que aconteça versus o que está realmente acontecendo, talvez eu possa dar uma olhada. (Além disso, aponte para as seções de documentos que você acha que são inconsistentes.) Até então, só posso atribuir isso a um mal-entendido de como o Redux funciona internamente.

Obrigado @markerikson .
Sobre a documentação:
image

Portanto, se o redutor for chamado com uma ação desconhecida, devo retornar ao mesmo estado que o redutor não sabe o que a ação deve fazer neste redutor.

Ao criar a loja, Redux verifica os redutores enviando-lhes uma ação desconhecida e estado anterior undefined . Portanto, se o estado anterior era undefined o redutor deve retornar undefined acordo com a documentação (porque o redutor não conhece a ação). Mas se o redutor retornar undefined , garanto que nenhum aplicativo Redux funcionaria.

Para o código de exemplo:

import { createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customReducer = (customData: any, action: any): any =>  {
        return customData;
}

const reducers = (currentState: IAppState, action: any): IAppState => {

    var appStateToEvaluate: any;
    if (currentState) { //needs to add this to pass the `undefined` check of redux
        appStateToEvaluate = currentState;
    }
    else{
        //why redux is doing this ?!
        appStateToEvaluate = {}
    }
    const newState: IAppState = {
        cvConfig: configReducer(appStateToEvaluate.config, action),
        personalData: customReducer(appStateToEvaluate.customData, action)
    }

    return newState;
}

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP : Acho que a principal diferença que você está perdendo aqui é que, naquele exemplo, a função redutora _already_ tratou do caso em que state é undefined , usando a sintaxe de argumentos padrão ES6:

function todoApp(state = initialState, action) {

Portanto, o conselho no tutorial está correto - um redutor _deve_ sempre retornar o estado existente, _assumindo que o caso indefinido já foi tratado_.

@markerikson
Obrigado pela sua resposta.
Isso está claro para mim. Só falta no tutorial. No tutorial não diz que o caso a ser retornado é aquele especificado como valor do parâmetro padrão no redutor. Portanto, a frase que você colocou em itálico está correta. Isso é isso. Minha preocupação é que está faltando no tutorial. Ou eu não encontrei, ou é ambíguo.

Agora, independentemente do tutorial e / ou documentação, acho que este cheque undefined pessoalmente não faz sentido para o caso específico em que um estado inicial é especificado. Se nenhum estado inicial for especificado, acho que está ok. Agora, isso não é uma discussão, apenas minha opinião: se um estado inicial for especificado, acho inútil fazer essa verificação. Mas eu posso (e tenho que) viver com isso de qualquer maneira;)

Obrigado por seu apoio Mark.

Certamente podemos tentar reformular algumas das frases como parte de nossa reformulação maior de documentos (# 2590).

Dito isso, uma das idéias originais por trás do Redux era que cada "redutor de fatia" é responsável por "possuir" sua parte do estado, o que inclui atualizações e fornecer um valor inicial. Você parece estar preso ao aspecto de "bem, estou fornecendo um valor inicial para createStore ", mas está descontando as expectativas de como o Redux espera que ele se comporte, mesmo que você _não_ fornecendo o valor separadamente.

Um tanto surpreso por não ter vinculado isso ainda, mas talvez você queira ler a página Estado de inicialização nos documentos.

@markerikson
Sim você está correto. Já conheço a documentação sobre o estado inicial. O que quero dizer (e acredito que seja a razão pela qual este problema foi criado e as pessoas também se referiram nessa direção) é que é contra-intuitivo fornecer "um estado inicial" ao criar a loja e ainda definir um "estado padrão" em os redutores de fatia (ou redutor de raiz). Ou seja, porque muitas vezes, mas não necessariamente, eles são iguais ou muito relacionados, parece contra-intuitivo ter que defini-los duas vezes. Veja como exemplo as postagens de @ElonXun ou @Vittly que se confundiram comigo. Ou seja, minha observação não é a API do Redux, é sobre como é intuitivo usar a API do Redux neste cenário específico, de uma perspectiva puramente humana.

Observe que o último parágrafo é sobre o sentimento humano ao usar a API do redux. A implementação do maquinário ou as razões por trás disso podem ser totalmente legítimas. Mas, como consumidor de API, parece confuso.

Por exemplo, para mim, quando desenvolvo, frequentemente tenho um estado inicial na minha aplicação. Normalmente, preciso digitar duas vezes. Uma vez para plugá-lo ao criar a loja e outra vez para distribuí-lo como valor padrão nos redutores de fatia. Claro, muitas soluções para isso. Mas o princípio que para mim, como humano, torna tudo confuso, é que tenho que digitar duas vezes a mesma coisa.

No entanto, acho que ter um caso especial em que o estado inicial é definido e não tornar obrigatório que os redutores de fatia ou redutor de raiz tenham um "estado padrão" é mais problemático para alguns do que ainda torná-lo obrigatório.

Portanto, a única contribuição aqui é mencionar que parece um pouco contra-intuitivo. Mas apenas isso.

Não me lembro de combineReducers chamar assertReducerShape em versões anteriores. No meu código, undefined é um estado inválido dos meus redutores. Não preciso combinarRedutores para garantir isso, preciso "combinar redutores" e recriar o objeto raiz se algo mudar. Está indo um pouco além do que eu esperava. IMO, é muito opinativo agora.

Admito que não uso combineReducers há algum tempo, pois não precisava deles no aplicativo principal que venho desenvolvendo. Mas agora estou tentando encapsular esse aplicativo e descobri que combineReducers pode ser usado para dividir o aplicativo. O comportamento de assertReducerShape é surpreendente.

@lukescott : esse cheque está lá desde setembro de 2015:

https://github.com/reduxjs/redux/commit/a1485f0e30ea0ea5e023a6d0f5947bd56edff7dd

E sim, combineReducers() é _deliberadamente_ opinativo. Se você não quiser esses avisos, é fácil escrever sua própria função semelhante sem essas verificações.

Como um exemplo específico, veja a essência do Redux de Dan

Entendo. A versão antiga passou do estado inicial e verificou se há indefinido em cada execução (se bem me lembro). O que é surpreendente é que o estado inicial não é passado na primeira execução. Passar indefinido é um erro em meu aplicativo. Como eu disse, estou trabalhando neste projeto há um tempo e não uso combineReducers há algum tempo. Agora estou voltando a usá-lo, colocando nosso aplicativo dentro de um invólucro.

Eu também entendo que é teimoso. Mas essa opinião era "um redutor não deve voltar indefinido" - que é a regra que eu obedeço. Foi alterado para "passaremos por indefinido e você mesmo deve criar o estado do nada". combineReducers não funciona mais com "sempre há um estado inicial" - o que é lamentável. Isso muda drasticamente as regras que estavam em vigor há 3 anos.

Eu realmente não tenho certeza de quais mudanças de comportamento você está se referindo. Você pode apontar exemplos específicos?

A página de documentos do estado de inicialização apresenta as interações entre o argumento preloadedState para createStore , a manipulação de state = initialState para um redutor e combineReducers() . Isso é baseado em uma resposta Stack Overflow que Dan escreveu no início de 2016, e nada significativo mudou a esse respeito que eu saiba.

@markerikson Você está certo. Eu olhei para trás até 2.0, e parece que sempre fez isso. Talvez a complexidade do meu aplicativo apenas tenha tornado isso mais aparente.

O problema que tenho é que meu redutor é definido como:

reducer(state: State, action: Action)

O que significa que esse estado não deve ser indefinido. Isso significa que deve haver um estado inicial. Mas, como combineReducers chama redutor (indefinido, INIT) para verificar, ele causa um erro no meu código (se for bem-sucedido, mais tarde chama redutor (initState, INIT) - chamando INIT duas vezes).

Isso significa que qualquer redutor usado em combineReducers DEVE ser definido como:

reducer(state: State | undefined, action: Action)

Portanto, minha afirmação de que isso não acontecia antes está incorreta. Mas o problema que eu tenho e o OP são provavelmente os mesmos: ele força você a declarar seus redutores com um estado opcional. Na verdade, eu não recebo os avisos, isso faz com que meu código trave porque espera um estado não indefinido.

Se você disser que deve ser assim e eu mesmo preciso rolar, tudo bem. É uma pena porque, além de afirmar a forma, ele já verifica se há indefinido em tempo de execução. Afirmar a forma parece um pouco exagero e contraproducente.

sim. Conforme descrito nessa página de documentos, quando você usa combineReducers , a expectativa é especificamente que cada redutor de fatia seja responsável por retornar seu próprio estado inicial e deve fazer isso em resposta a sliceReducer(undefined, action) . Do ponto de vista de combineReducers , seu código _está_ cheio de erros porque não está aderindo ao contrato esperado e, portanto, está informando que o código está errado.

Você não está realmente fornecendo um estado inicial? Qual a aparência real do código?

Estou fornecendo um estado inicial por meio do createStore. Mas esse estado inicial não está sendo passado para meu redutor porque assertReducerShape está chamando explicitamente meu redutor com undefined , apesar de eu passar um estado inicial:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L68

Meu código não está cheio de erros. Requer apenas que undefined _nunca_ seja passado para ele. Um estado inicial é necessário e gerado a partir do servidor. combineReducers apenas quebra esse contrato e torna impossível digitar estritamente o redutor com um estado exigido. Posso fazer isso sem combinarRedutores e funciona bem. Eu acho que é o que terei que fazer - mas IMO - forçar um estado opcional é indesejável e, no meu caso, torna combineReducers inútil porque quebra meu aplicativo estritamente digitado.

O que não entendo é por que assertReducerShape é necessário. Ele já verifica undefined aqui no tempo de execução:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L169

assertReducerShape parece meio redundante. Mas, no meu caso, o tipo de quebra garante ... o que ele está tentando afirmar?

Então, esse é realmente um problema de interação do TypeScript?

Para ser honesto, isso parece, em última análise, uma diferença de abordagens.

combineReducers() foi escrito em JS e está tentando fazer verificações de tempo de execução.

Você está tentando fazer verificações estáticas no TS e a declaração que você escreveu não corresponde ao modo como combineReducers() realmente funciona. Portanto, nesse sentido, a declaração de tipo para seu redutor de fatia está incorreta, porque _pode_ e _será_ chamado com undefined quando usado com combineReducers() .

A linha específica que você chamou está verificando se o valor de retorno de seu redutor de fatia não é undefined quando chamado com um valor de fatia de estado existente (presumivelmente significativo), enquanto assertReducerShape() está verificando se não retorna undefined quando dado um valor de estado inicial de undefined (ou seja, que o redutor auto-inicializa seu estado) e também quando chamado com um tipo de ação desconhecido (ou seja, que o redutor sempre retorna o estado existente por padrão).

se está ficando indefinido, então troque ... no meu caso, quando meu servidor responder sem os dados por algum motivo, talvez porque não haja sessão ativa, estou com aquele problema, então fazendo isso no redutor, funcionou como um encanto para mim:

export default function itemReducer(state = initialState.items | undefined, action){ 
    switch(action.type) 
   { 
         case "LOAD_ITEMS_SUCCESS":
               if(action.items==undefined) { 
                        action.items=[]; 
               }
                return action.items

Se o redutor ficar indefinido, substitua undefined por um array vazio e você não receberá mais esse erro.

Felicidades!

Esta página foi útil?
0 / 5 - 0 avaliações