Redux: Doy el initialState en createStore y luego combineReducers muestre uno de mis reductores devuelto sin definir durante la inicialización

Creado en 23 ago. 2017  ·  27Comentarios  ·  Fuente: reduxjs/redux

en reductores:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

en blogTypeVisibilityFilter:

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

export default blogTypeVisibilityFilter;

en blogs:

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

en createStore:

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

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

y luego se muestra mal:

El reductor "blogTypeVisibilityFilter" devolvió indefinido durante la inicialización. Si el estado pasado al reductor no está definido, debe devolver explícitamente el estado inicial. El estado inicial no puede estar indefinido. Si no desea establecer un valor para este reductor, puede usar nulo en lugar de indefinido.

pero cuando acabo de cambiar

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

a

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

en reductores funciona bien y sin errores

¿Por qué uso combineReducers si sale mal?

Comentario más útil

Este es un rastreador de errores, no un sistema de soporte. Si tiene preguntas sobre el uso, utilice Stack Overflow o Reactiflux. ¡Gracias!

Todos 27 comentarios

Este es un rastreador de errores, no un sistema de soporte. Si tiene preguntas sobre el uso, utilice Stack Overflow o Reactiflux. ¡Gracias!

Yo tengo el mismo problema.

Pasos:

  1. Imagen con forma de tienda simple { foo, bar } .
  2. Cree un reductor simple como simpleReducer = state => state .
  3. Combine reductores en reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }) .
  4. Cree un estado con valor inicial con createState(reducers, { foo: Obj1, bar: Obj2 }) .
  5. Obtiene el error Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

La razón es assertReducerShape que ejecutan todos los reductores con un estado indefinido para obtener las partes del estado inicial. Mi simple reductor devuelve su argumento que no está definido en ese caso. ¿Por qué Obj1 de createState call no se considera como estado inicial de foo-part?

@timdorr, ¿puedes reabrir el problema?

La solución alternativa es hacer simpleReducer = (state = null) => state . ¿Pero está bien?

Con combineReducers , se espera que cada reductor de rebanadas "posea" su porción de estado. Eso significa proporcionar un valor inicial en caso de que el segmento actual no esté definido, y combineReducers señalará si no lo está haciendo.

'Estado inicial' tiene múltiples significados. Son el valor de retorno predeterminado de reducer y el segundo argumento de createStore. Creo que hay un problema. ¿Puede explicar por qué combineReducers comprueba los reductores para initialState y createState no?

@Vittly : combineReducers es deliberadamente obstinado, mientras que createStore no lo es. createStore simplemente llama a la función reductora de raíz que le haya asignado y guarda el resultado. combineReducers asume que si lo está usando, está comprando un conjunto específico de suposiciones sobre cómo debe organizarse el estado y cómo deben comportarse los reductores combinados.

Es posible que desee leer los números 191 y 1189, que explican el historial y los detalles de por qué combineReducers tiene opiniones y qué opiniones tiene.

Entonces, el problema es (en breve) "si combina reductores, también divide su árbol de tiendas y puede perder algo o precargar deliberadamente solo una parte de la tienda en createStore 2nd arg. Para hacer que el árbol de tiendas sea más consistente, use assertReducerShape". De acuerdo, gracias por las referencias.

El mismo problema aqui.
Entonces uso createStore y paso un estado inicial. Pero en el archivo reduct.js , la función assertReducerShape redux comprobará mis reductores cuando pase un estado undefined .

En mi humilde opinión, esto es un error.

@JoseFMP : Garantizaría efectivamente que lo que sea que esté viendo _no_ es un error. Sin embargo, si puede armar un repositorio o CodeSandbox que demuestre el problema, podemos echarle un vistazo.

Seguro. Presenté un problema en cuestión en Stackoverflow:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

El problema es bastante sencillo. Entonces, si establezco un estado inicial al crear la tienda ... ¿por qué redux querría evaluar el reductor con un estado undefined ? Eso hace que todos los reductores devuelvan un nuevo estado undefined .

Pero es una tontería, ya que pasamos por un estado inicial.
Entonces esto está fallando:

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, creo que sé cuál es el problema. Esto es específico de cómo funciona combineReducers() .

combineReducers espera que todos los "reductores de corte" que le proporciones sigan un par de reglas. En particular, espera que _si_ su reductor es llamado con state === undefined , devolverá un valor predeterminado adecuado. Para verificar esto, combineReducers() realmente llamará a su reductor con (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) para ver si devuelve un valor indefinido o "real".

Sus reductores actualmente no hacen nada excepto devolver lo que se haya pasado. Eso significa que si state no está definido, _retornarán_ indefinido. Entonces, combineReducers() está diciendo que está rompiendo el resultado que espera.

Simplemente cambie las declaraciones a algo como configReducer = (config = {}, action) , etc., y eso solucionará las advertencias.

Nuevamente, para que quede claro: esto _no_ es un error. Esta es la validación del comportamiento.

Hej @markerikson gracias por tu respuesta.
Algunos comentarios:

1) No, NO es específico de combineReducers . Si no uso combineReducers e implemento mi propio reductor de raíz, sucede lo mismo.

2) La inconsistencia aquí es que, en la documentación de Redux se menciona que si se llama al reductor con un estado desconocido o inesperado, no debe modificar el estado y devolver el valor que vino como argumento. Es decir ... si recibe undefined , entonces debería devolver undefined . Pero otra regla dice que el reductor nunca debe devolver undefined por lo que estas dos reglas, como se encuentran actualmente en la documentación de redux, son inconsistentes. E inconsistente con la implementación.

@JoseFMP : como dije anteriormente, si puede proporcionar un CodeSandbox que demuestre específicamente el problema, con comentarios que indiquen exactamente lo que espera que suceda frente a lo que realmente está sucediendo, tal vez pueda echar un vistazo. (Además, señale las secciones de documentos que crea que son inconsistentes). Hasta entonces, solo puedo atribuir esto a un malentendido de cómo funciona Redux internamente.

Gracias @markerikson .
Sobre la documentación:
image

Entonces, si se llama al reductor con una acción desconocida, debería devolver el mismo estado, ya que el reductor no sabe qué debe hacer la acción en este reductor.

Al crear la tienda, Redux verifica los reductores enviándoles una acción desconocida y el estado anterior undefined . Entonces, si el estado anterior era undefined el reductor debería devolver undefined acuerdo con la documentación (porque el reductor no conoce la acción). Pero si el reductor devuelve undefined , le garantizo que ninguna aplicación Redux podría funcionar.

Para el código de ejemplo:

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 : Creo que la diferencia clave que se está perdiendo aquí es que en ese ejemplo, la función reductora ya ha manejado el caso en el que state es undefined , utilizando la sintaxis de argumentos predeterminada de ES6:

function todoApp(state = initialState, action) {

Entonces, el consejo del tutorial es correcto: un reductor _debería_ siempre devolver el estado existente, _suponiendo que el caso indefinido ya se ha manejado_.

@markerikson
Gracias por su respuesta.
Eso está claro para mí. Solo falta en el tutorial. En el tutorial no dice que el caso a devolver sea el especificado como valor de parámetro predeterminado en el reductor. Entonces, la oración que puso en cursiva es correcta. Eso es eso. Mi preocupación es que falta en el tutorial. O no lo encontré, o es ambiguo.

Ahora, independientemente del tutorial y / o la documentación, encuentro que este cheque undefined personalmente no tiene sentido para el caso específico en el que se especifica un estado inicial. Si no se especifica un estado inicial, encuentro que está bien. Ahora bien, esto no es una discusión, solo mi opinión: si se especifica un estado inicial, no tiene sentido hacer esta verificación. Pero puedo (y tengo que) vivir con eso de todos modos;)

Gracias por tu apoyo Mark.

Ciertamente podemos intentar reformular algunas de las frases como parte de nuestra renovación de documentos más grande (# 2590).

Dicho esto, una de las ideas originales detrás de Redux era que cada "reductor de rebanadas" es responsable de "poseer" su parte del estado, que incluye tanto actualizaciones como proporcionar un valor inicial. Pareces estar un poco atascado en el aspecto de "bueno, estoy proporcionando un valor inicial a createStore ", pero estás descontando las expectativas de cómo Redux espera que se comporte incluso si tú _no_ proporcionando el valor por separado.

Algo sorprendido de que no vincule esto todavía, pero es posible que desee leer la página Estado de inicialización en los documentos.

@markerikson
Sí, estás en lo correcto. Ya conozco la documentación sobre el estado inicial. Lo que quiero decir (y creo que es la razón por la que se creó este problema y la gente también quiso decir en esta dirección) es que es contrario a la intuición proporcionar "un estado inicial" al crear la tienda y aún así definir un "estado predeterminado" en los reductores de rebanadas (o reductor de raíces). Es decir, porque muy a menudo, pero no necesariamente, son iguales, o están muy relacionados, parece contrario a la intuición tener que definirlos dos veces. Vea como ejemplo las publicaciones de @ElonXun o @Vittly que se confundieron al igual que yo. Es decir, mi observación no es la API de Redux, se trata de lo intuitivo que se siente al usar la API de Redux en este escenario en particular, desde una perspectiva puramente humana.

Tenga en cuenta que el último párrafo trata sobre el sentimiento humano al usar la API de redux. La implementación de la maquinaria o las razones detrás de ella pueden ser totalmente legítimas. Pero como consumidor de API, se siente confuso.

Por ejemplo, para mí, cuando desarrollo con mucha frecuencia tengo un estado inicial en mi aplicación. Entonces, por lo general, necesito escribirlo dos veces. Una vez para enchufarlo cuando creo la tienda y otra vez para distribuirlo como valor predeterminado en los reductores de rebanadas. Por supuesto, muchas soluciones para eso. Pero el principio que me confunde como humano es que tengo que escribir dos veces lo mismo.

Sin embargo, creo que tener un caso especial en el que se establece el estado inicial y no hacer obligatorio que los reductores de rebanadas o el reductor de raíz tengan un "estado predeterminado" es más problemático que hacerlo obligatorio.

Entonces, la única contribución aquí es mencionar que se siente un poco contradictorio. Pero solo eso.

No recuerdo que combineReducers llame a assertReducerShape en versiones anteriores. En mi código undefined es un estado inválido de mis reductores. No necesito combineReducers para asegurarme esto, lo necesito para "combinar reductores" y volver a crear el objeto raíz si algo cambia. Va un poco más allá de lo que esperaría que hiciera. En mi opinión, ahora es demasiado obstinado.

Es cierto que no he usado combineReducers en algún tiempo porque no lo he necesitado en la aplicación principal que he estado desarrollando. Pero ahora estoy tratando de ajustar esa aplicación, y pensé que combineReducers estaba bien para usar para dividir la aplicación. El comportamiento de assertReducerShape es sorprendente.

@lukescott : ese cheque ha estado allí desde septiembre de 2015:

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

Y sí, combineReducers() es _deliberadamente_ obstinado. Si no desea esas advertencias, es bastante fácil escribir su propia función similar sin esas comprobaciones.

Como ejemplo específico, vea la esencia Redux de Dan

Lo entiendo. La versión anterior pasó el estado inicial y se comprobó que no estuviera definida en cada ejecución (si mal no recuerdo). Lo sorprendente es que el estado inicial no se transmite en la primera ejecución. Pasar indefinido es un error en mi aplicación. Como dije, he estado trabajando en este proyecto por un tiempo y no he usado combineReducers por un tiempo. Ahora estoy volviendo a usarlo, colocando nuestra aplicación dentro de un contenedor.

También entiendo que es obstinado. Pero esa opinión era "un reductor no debe volver indefinido", que es la regla que cumplo. Ha cambiado a "pasaremos sin definir y debes crear el estado tú mismo a partir de la nada". combineReducers ya no funciona con "siempre hay un estado inicial", lo cual es lamentable. Eso cambia drásticamente las reglas que estaban en vigor hace 3 años.

Realmente no estoy seguro de a qué cambios de comportamiento te refieres. ¿Puede señalar ejemplos específicos?

La página de documentos Initializing State establece las interacciones entre el argumento preloadedState para createStore , el manejo state = initialState para un reductor y combineReducers() . Eso se basa en una respuesta de Stack Overflow que Dan escribió a principios de 2016, y nada significativo ha cambiado en ese sentido que yo sepa.

@markerikson Tienes razón. Miré hacia atrás hasta 2.0, y parece que siempre ha hecho esto. Quizás la complejidad de mi aplicación lo haya hecho más evidente.

El problema que tengo es que mi reductor se define como:

reducer(state: State, action: Action)

Lo que significa que el estado no debe ser indefinido. Eso significa que tiene que haber un estado inicial. Pero debido a que combineReducers llama a reducer (undefined, INIT) para verificarlo, causa un error en mi código (si tiene éxito, luego llama a reducer (initState, INIT) - llamando a INIT dos veces).

Esto significa que cualquier reductor que se utilice en la combinación de reductores DEBE definirse como:

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

Entonces, mi afirmación de que no hizo eso antes es incorrecta. Pero el problema que tengo, y el OP, es probablemente el mismo: te obliga a declarar tus reductores con un estado opcional. En realidad, no recibo las advertencias, hace que mi código se bloquee porque espera un estado no indefinido.

Si dices que debe ser así y necesito enrollarlo yo mismo, está bien. Es lamentable porque, además de afirmar la forma, ya busca indefinido en tiempo de ejecución. Afirmar la forma parece un poco exagerado y contraproducente.

Si. Como se describe en esa página de documentos, cuando usa combineReducers , la expectativa es específicamente que cada reductor de rebanadas sea responsable de devolver su propio estado inicial, y debería hacerlo en respuesta a sliceReducer(undefined, action) . Desde el punto de combineReducers , su código _ está_ defectuoso porque no se adhiere al contrato esperado y, por lo tanto, le está diciendo que el código es incorrecto.

¿No está proporcionando realmente un estado inicial? ¿Cómo se ve realmente el código?

Estoy proporcionando un estado inicial a través de createStore. Pero ese estado inicial no se pasa a mi reductor porque assertReducerShape llama explícitamente a mi reductor con undefined , a pesar de que paso un estado inicial:

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

Mi código no tiene errores. Solo requiere que indefinido _nunca_ se pase a él. Se requiere un estado inicial y se genera desde el servidor. combineReducers simplemente rompe ese contrato y hace imposible escribir estrictamente el reductor con un estado requerido. Puedo hacer esto sin combineReducers y funciona bien. Supongo que eso es lo que tendré que hacer, pero en mi opinión, forzar un estado opcional no es deseable y, en mi caso, hace que combineReducers sea inútil porque rompe mi aplicación estrictamente escrita.

Lo que no entiendo es por qué es necesario assertReducerShape. Ya comprueba undefined aquí en tiempo de ejecución:

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

assertReducerShape parece algo redundante. Pero en mi caso, rompe las garantías de tipo ... ¿Qué es lo que intenta hacer valer?

Entonces, ¿esto es realmente un problema de interacción de TypeScript?

Para ser honesto, esto parece ser, en última instancia, una diferencia de enfoques.

combineReducers() se escribió en JS y está intentando realizar comprobaciones en tiempo de ejecución.

Está intentando realizar comprobaciones estáticas en TS y la declaración que ha escrito no coincide con el funcionamiento real de combineReducers() . Entonces, en ese sentido, la declaración de tipo para su reductor de rebanadas es incorrecta, porque _puede_ y _se llamará_ con undefined cuando se use con combineReducers() .

La línea específica que ha llamado es verificar que el valor de retorno de su reductor de rebanadas no sea undefined cuando se llama con un valor de porción de estado existente (presumiblemente significativo), mientras que assertReducerShape() está comprobando que sí no devuelve undefined cuando se le da un valor de estado inicial de undefined (es decir, que el reductor inicializa automáticamente su estado) y también cuando se llama con un tipo de acción desconocido (es decir, que el reductor siempre devuelve el estado existente por defecto).

si no está definido, reemplácelo ... en mi caso, cuando mi servidor responde sin los datos por cualquier motivo, tal vez porque no hay una sesión activa, tengo ese problema, así que hacer esto en el reductor funcionó como un encanto para mi:

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

Si el reductor no está definido, reemplace undefined por una matriz vacía y ya no obtendrá ese error.

¡Salud!

¿Fue útil esta página
0 / 5 - 0 calificaciones