Redux: Componentes que no se vuelven a renderizar con connect ()

Creado en 20 ago. 2015  ·  32Comentarios  ·  Fuente: reduxjs/redux

Estoy conociendo Redux después de leer los documentos y estoy comenzando a modificar el ejemplo del mundo real. En mi caso, cambié las llamadas a la API HTTP de GitHub por las mías y cambié los conceptos de Repo y User por otros análogos en mi entorno. Hasta ahora, no debería ser muy diferente, ¿verdad?

Aquí es donde me estoy metiendo en problemas:

Mis llamadas a la API se realizan correctamente, la respuesta JSON se está normalizando y se basa en camello, pero mi componente no se vuelve a procesar después de que la solicitud de la API se realiza correctamente. Poco después de que se envíe USER_REQUEST y el componente de usuario se actualice para mostrar "Cargando ...", se envía USER_SUCCESS y su siguiente estado contiene una clave entities poblada:

pasted_image_8_19_15__3_27_pm

Eso significaría que el problema está en el middleware, ¿verdad? Debería almacenar las entidades de la acción en la tienda, y el componente debería observar el cambio y volver a renderizar, ¿no?

¿Dónde, en el ejemplo del mundo real, los datos recuperados y normalizados con éxito _en realidad_ se colocan en la tienda? Veo dónde se normaliza, pero no dónde se entrega a la tienda el resultado de esa normalización.

¡Muchas gracias!

question

Comentario más útil

Los datos se configuran / actualizan / eliminan en la tienda a través de los resultados de las acciones de manejo en los reductores. Los reductores reciben el estado actual de una porción de su aplicación y esperan recuperar el nuevo estado. Una de las razones más comunes por las que sus componentes podrían no volver a renderizarse es que está modificando el estado existente en su reductor en lugar de devolver una nueva copia del estado con los cambios necesarios (consulte la sección Solución de problemas ). Cuando mutas el estado existente directamente, Redux no detecta una diferencia de estado y no notifica a tus componentes que la tienda ha cambiado.

Así que definitivamente revisaría sus reductores y me aseguraré de que no esté mutando el estado existente. ¡Espero que ayude!

Todos 32 comentarios

Los datos se configuran / actualizan / eliminan en la tienda a través de los resultados de las acciones de manejo en los reductores. Los reductores reciben el estado actual de una porción de su aplicación y esperan recuperar el nuevo estado. Una de las razones más comunes por las que sus componentes podrían no volver a renderizarse es que está modificando el estado existente en su reductor en lugar de devolver una nueva copia del estado con los cambios necesarios (consulte la sección Solución de problemas ). Cuando mutas el estado existente directamente, Redux no detecta una diferencia de estado y no notifica a tus componentes que la tienda ha cambiado.

Así que definitivamente revisaría sus reductores y me aseguraré de que no esté mutando el estado existente. ¡Espero que ayude!

Además de lo que dijo @ernieturner , asegúrese de que la función mapStateToProps en su función connect(mapStateToProps)(YourConnectedComponent) mapee el estado relevante (en este caso entities ) para que el componente conectado esté escuchando a esa porción del estado.

Además, tenga en cuenta que connect() hace una comparación superficial: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

Es probable que debido a que la forma de su estado conectado no haya cambiado, se asume que no debe realizar una actualización.

¡Ah, lo tengo! Mi mapStateToProps intentaba extraer un objeto del estado, pero no especificaba la clave correcta. En lugar de extraer entities.users[login] , lo configuré en entities.users[id] . El objeto entities.users fue codificado por login cadena, por lo que cuando no se encontró el id numérico, no se cambiaron los accesorios, por lo que no fue necesario volver a renderizar.

¡Gracias por toda la ayuda! Realmente aprecio el tiempo que se tomó para ayudar.

@timdorr
Eso es ridículo. ¿Cómo forzar connect() verificar cambios profundos? De lo contrario, uno tiene que crear docenas de componentes de contenedor para cada nivel más profundo de estado. No es normal

Debe tener muchos componentes conectados en cualquier aplicación de tamaño razonable. Si tiene un solo componente conectado de nivel superior, volverá a renderizar grandes partes de su aplicación innecesariamente cada vez que el estado cambie. Y estará obviando una de las principales optimizaciones de react-redux, que es volver a renderizar solo cuando cambia el estado al que está suscrito.

Si necesita evaluar una consulta de estado compleja, utilice reselect para acelerarla.

@timdorr @wzup

Puede ser útil aclarar que la conexión en los lugares más bajos del árbol no es estrictamente necesaria para resolver el problema de la comparación profunda.

Conectarse en lugares más bajos en su árbol de componentes puede ayudar con el _ rendimiento_, pero no es la solución fundamental al problema de comparación profunda. La solución a los problemas de comparación profunda es que los reductores actualicen el estado de manera inmutable, de modo que una comparación superficial sea adecuada para saber cuándo cambió una parte más profunda del estado.

En otras palabras, conectar un componente de nivel superior a una gran parte de estado anidada funciona bien, incluso con comparaciones superficiales, siempre que los reductores devuelvan un nuevo estado sin alterar los objetos de estado existentes.

@naw Gracias por ese consejo. En base a eso, terminé agregando un nuevo reductor donde estaba experimentando este problema:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

Sin embargo, esto todavía me parece extraño. Si tengo un reductor que dice:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

el hecho de que estoy devolviendo un objeto recién inicializado que tiene la misma forma que el estado anterior debería ser suficiente para activar un previousState !== newState . Si se supone que debo devolver datos inmutables cada vez, parece que es más ineficaz hacer una comparación superficial de la forma en lugar de solo la identidad. (Suponiendo que eso es lo que Redux está haciendo realmente, eso es. El hecho de que mi solución Symbol funcione me hace pensar que eso es lo que está pasando)

En este caso particular, agregar más componentes de contenedor no resuelve el problema. Lamentablemente, no puedo vincularme a un proyecto completo de github para una aclaración porque estas son las cosas de código cerrado de mi empresa con las que estoy tratando aquí.

@Zacqary La comparación del estado actual con el estado anterior se realiza con estricto igual (===). La comparación de stateProps (el resultado de mapStateToProps) se realiza con igual superficial.

@Zacqary

mapStateToProps está "ensamblando" datos para su componente conectado al ingresar a la tienda.

mapStateToProps saca varias piezas de la tienda y las asigna a claves en un nuevo objeto stateProps .

El objeto stateProps resultante será nuevo cada vez (por lo que su identidad cambia), por lo que no podemos simplemente comparar la identidad del objeto del propio objeto stateProps --- tenemos que bajar uno level y comprobar la identidad del objeto de cada valor, que es donde entra en juego shallowEqual , como dijo @jimbolla .

Por lo general, surgen problemas cuando escribe reductores de tal manera que la identidad del objeto de una parte del estado _no cambia_, mientras que algún atributo anidado dentro de él _ sí_ cambia. Este es un estado mutante en lugar de devolver un nuevo estado de manera inmutable, lo que hace que react-redux piense que nada cambió, cuando de hecho, algo _ sí_ cambió.

el hecho de que estoy devolviendo un objeto recién inicializado que tiene la misma forma que el estado anterior debería ser suficiente para activar un estado previo! == newState.

Si devuelve un nuevo objeto para algún segmento de estado, y mapStateToProps usa ese segmento de estado como valor para una de sus claves, entonces react-redux verá que la identidad del objeto cambió y no evitar una re-renderización.

¿Hay algo específico que no esté funcionando como esperabas? No estoy muy seguro de a qué te refieres con tu solución Symbol .

Vale la pena considerar si el componente adicional simplemente debe moverse hacia abajo en el componente connected() para heredar los accesorios. Llegué a este problema al intentar connect() dos componentes, pero eso era una complejidad innecesaria.

@ernieturner Sé que esto es antiguo, pero es posible que desee actualizar el enlace de la Sección de solución de problemas .

Salud

Tuve este problema, bueno, no exactamente.
Apliqué las propiedades al estado, como:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

Y no estaba funcionando. Así que apliqué valor directamente desde accesorios como

{this.props.text}

Y funciona bien :)

@AlexanderKozhevin A menos que me equivoque, a su constructor le falta un super(props) .
Normalmente, ni siquiera se le permite usar this antes de llamar a super(props) .

@ cdubois-mh Tienes razón, gracias por la nota.

He escrito un reductor de raíz de aplicaciones a medida y tengo ese problema.
Devolver una copia de todo el estado
{...state}
al final, el reductor de raíces me ayudó

@daedalius Así es como se supone que funciona un reductor.
Debería devolver el estado si la acción es irrelevante o devolver una copia del estado con los valores modificados esperados.

Si no devuelve una copia, react no siempre podrá detectar que un valor cambió.
(por ejemplo, si modifica una entrada anidada)

Marque este estado:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

Si modifica el nombre de un cliente, pero no devuelve un clon del estado, entonces no modificó el estado, modificó el objeto en la matriz.

El objeto en sí seguirá teniendo la misma referencia que antes, por lo que reaccionar perderá el cambio.
Al devolver un clon, la referencia del estado en sí será ahora diferente (ya que es un objeto nuevo) y reaccionar iniciará todo el proceso de renderizado.

Si me equivoco, corríjame, pero así es como entiendo que funcionan los reductores.

Sí, eso es correcto. Además, @daedalius , tenga en cuenta que no desea _siempre_ hacer una copia superficial de state ; solo debe hacerlo si algo cambia, de lo contrario puede terminar con una renderización innecesaria de sus componentes.

forzar actualización

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee ¿Qué se supone que significa eso? no proporcionaste ninguna información o explicación de tu código. ¿Qué hace? ¿Por qué es relevante para la discusión actual que nos ocupa?

@CedSharp La función shallowEqual devolverá falso, si configuramos _forceUpdate = {Symbol ()}

@BrookShuihuaLee Sería genial si pudieras incluir una explicación como esta en tu primera respuesta, de esta manera la gente lo entenderá mejor ^^

@CedSharp

@BrookShuihuaLee : Parece una mala idea. ¿Por qué querrías hacer eso?

@BrookShuihuaLee , estoy de acuerdo con @markerikson .
Hay una razón por la que se supone que NO debe devolver un clon cuando el estado no cambió. A menos que pueda proporcionar un ejemplo válido en el que su solución parece ser necesaria, le recomiendo encarecidamente que no vaya por ahí cómo funciona Redux.

@CedSharp
@markerikson
Porque el componente AfterConnect se volverá a reproducir con frecuencia, por diseño. Está bien volver a renderizarlo una vez más cuando se vuelve a renderizar su componente principal. No quiero hacer todos los cálculos en el componente principal y establecer toneladas de accesorios para AfterConnect.

@BrookShuihuaLee , lo siento, pero ¿cómo es su componente relevante para la discusión en cuestión?
Si lo entiendo correctamente, ¿está hablando de su propio componente personalizado? ForceUpdate es algo muy desaconsejado en el mundo de React y no lo recomendaría como solución.

En su lugar, ¿podría usar una solución observable en la que busque cambios en otro lugar que no sea el estado? No sé por qué necesitaría volver a renderizar ese componente específico sin que se modifique su estado, pero si sabe por qué debería volver a renderizarse, tal vez podría usar algo como mobx.
(https://github.com/mobxjs/mobx)

Estoy tratando de entender cuál es su solución y por qué se aplica en esta situación.

@CedSharp No me refiero a que todos deberían usar forceUpdate. Pero la recomendación es una cosa, la elección es otra. React también mantiene ReactComponent.prototype.forceUpdate para desarrolladores. La gente necesita opciones.

Estaba tratando de dar otra opción. Tal vez no sea la mejor opción.

No es óptimo, pero me he encontrado con este problema porque he estructurado el estado de mi aplicación de forma incorrecta. Solucioné el problema de la comparación superficial simplemente actualizando un campo poco profundo en la porción del estado que me estaba causando problemas.

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

En mi opinión, no es una buena solución, pero para cualquier persona que se encuentre actualmente en un aprieto (es decir, tiene que lanzar al final del día), es una solución rápida y no tiene que forzar un renderizado en otro lugar.

El código debe refactorizarse para que funcione una comparación superficial, incluso en un estado profundamente anidado.

El estado debe ser inmutable. Copie profundamente el estado en el reductor, luego la comparación superficial en la conexión es suficiente y efectiva.

Mi problema era tener un objeto con matrices dentro. Como era una comparación superficial, nunca llegó a los objetos dentro de las matrices secundarias. Lo resolví usando list:state.obj.list lugar de lists:state.obj en mapStateToProps 👍🏼

Me acabo de encontrar con este problema, y ​​después de leer las primeras respuestas, revisé mi reductor y noté que tenía un error tipográfico allí. Cambié el nombre de una propiedad y actualicé INITIAL_STATE pero olvidé cambiar la clave que estaba siendo actualizada por esta acción problemática.

Básicamente, estaba actualizando alguna clave aleatoria que no se usó en el componente, así que obviamente, no volvería a renderizarse cuando se actualizara esta clave aleatoria.

¡COMPRUEBE SI HAY ERRORES EN SU REDUCTOR! :)

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

Temas relacionados

timdorr picture timdorr  ·  3Comentarios

amorphius picture amorphius  ·  3Comentarios

dmitry-zaets picture dmitry-zaets  ·  3Comentarios

vslinko picture vslinko  ·  3Comentarios

mickeyreiss-visor picture mickeyreiss-visor  ·  3Comentarios