¿Cómo sabe un componente conectado redux cuándo volver a renderizar?

86

Probablemente me esté perdiendo algo muy obvio y me gustaría aclararme.

Aquí está mi comprensión.
En un componente de reacción ingenua, tenemos states& props. La actualización statecon setStatevuelve a renderizar todo el componente. propsen su mayoría son de solo lectura y actualizarlos no tiene sentido.

En un componente de reacción que se suscribe a una tienda redux, a través de algo como store.subscribe(render), obviamente se vuelve a renderizar cada vez que se actualiza la tienda.

react-redux tiene un ayudante connect()que inyecta parte del árbol de estado (que es de interés para el componente) y actionCreators en cuanto propsal componente, generalmente a través de algo como

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Sin embargo, con el entendimiento de que setStatees esencial para la TodoListComponentque reaccionan al cambio árbol del estado redux (re-render), no puedo encontrar ninguna stateo setStatecódigo relacionado en el TodoListarchivo del componente. Lee algo como esto:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

¿Alguien puede indicarme la dirección correcta en cuanto a lo que me estoy perdiendo?

PD: Estoy siguiendo el ejemplo de la lista de tareas pendientes incluido con el paquete redux .

Joe Lewis
fuente

Respuestas:

109

La connectfunción genera un componente contenedor que se suscribe a la tienda. Cuando se envía una acción, se notifica la devolución de llamada del componente contenedor. Luego ejecuta su mapStatefunción y compara superficialmente el objeto de resultado de este momento con el objeto de resultado de la última vez (por lo que si tuviera que reescribir un campo de tienda redux con su mismo valor, no desencadenaría una nueva representación). Si los resultados son diferentes, pasa los resultados a su componente "real" como accesorios.

Dan Abramov escribió una gran versión simplificada de connectat ( connect.js ) que ilustra la idea básica, aunque no muestra nada del trabajo de optimización. También tengo enlaces a varios artículos sobre el rendimiento de Redux que discuten algunas ideas relacionadas.

actualizar

React-Redux v6.0.0 realizó algunos cambios internos importantes en la forma en que los componentes conectados reciben sus datos de la tienda.

Como parte de eso, escribí una publicación que explica cómo funcionan la connectAPI y sus componentes internos, y cómo han cambiado con el tiempo:

Redux idiomático: la historia y la implementación de React-Redux

markerikson
fuente
¡Gracias! ¿Eres la misma persona que me ayudó el otro día @ HN - news.ycombinator.com/item?id=12307621 con enlaces útiles? Encantado de conocerte :)
Joe Lewis
4
Sí, ese soy yo :) Me paso manera demasiado tiempo en busca de los debates sobre Redux en línea - Reddit, HN, SO, Medio y varios otros lugares. ¡Encantado de ayudar! (También para su información, recomiendo encarecidamente los canales de chat de Reactiflux en Discord. Mucha gente pasando el rato y respondiendo preguntas. Por lo general, estoy en línea por las tardes de EE. UU. EST. El enlace de invitación está en reactiflux.com .)
markerikson
Suponiendo que esto respondiera a la pregunta, ¿le importaría aceptar la respuesta? :)
markerikson
¿Está comparando los objetos en sí mismos o las propiedades de los objetos antes / después? Si es lo último, ¿solo marca el primer nivel? ¿Es eso lo que quiere decir con "superficial"?
Andy
Sí, un medio de verificación "igualdad superficial" para comparar los primeros campos de nivel de cada objeto: prev.a === current.a && prev.b === current.b && ...... Eso supone que cualquier cambio de datos dará como resultado nuevas referencias, lo que significa que se requieren actualizaciones de datos inmutables en lugar de una mutación directa.
markerikson
13

Mi respuesta está un poco fuera de lugar. Arroja luz sobre un problema que me llevó a esta publicación. En mi caso, parecía que la aplicación no se estaba volviendo a renderizar, a pesar de que recibió nuevos accesorios.
Los desarrolladores de React tuvieron una respuesta a esta pregunta que se hace a menudo, algo así como que si la (tienda) estaba mutada, el 99% de las veces esa es la razón por la que React no volverá a renderizarse. Sin embargo, nada sobre el otro 1%. La mutación no fue el caso aquí.


TLDR;

componentWillReceivePropses cómo statese puede mantener sincronizado con el nuevo props.

Edge Caso: Una vez que statelas actualizaciones, a continuación, la aplicación no volver a hacer!


Resulta que si su aplicación se usa solo statepara mostrar sus elementos, se propspuede actualizar, pero stateno lo hará, por lo que no se vuelve a renderizar.

Tenía stateque dependía de lo propsrecibido de redux store. Los datos que necesitaba aún no estaban en la tienda, así que los obtuve componentDidMount, como corresponde. Recuperé los accesorios, cuando mi reductor actualizó la tienda, porque mi componente está conectado a través de mapStateToProps. Pero la página no se procesó y el estado todavía estaba lleno de cadenas vacías.

Un ejemplo de esto es decir que un usuario cargó una página de "editar publicación" desde una URL guardada. Tienes acceso al postIddesde la URL, pero la información storeaún no está , así que la obtienes. Los elementos de su página son componentes controlados, por lo que todos los datos que muestra están en formato state.

Con redux, se obtuvieron los datos, se actualizó la tienda y se editó el componente connect, pero la aplicación no reflejó los cambios. En una mirada más cercana, propsse recibieron, pero la aplicación no se actualizó. stateno se actualizó.

Bueno, propsse actualizará y se propagará, pero stateno lo hará. Debe indicar específicamente stateque se actualice.

No puedes hacer esto render()y componentDidMountya terminaste sus ciclos.

componentWillReceivePropses donde actualiza las statepropiedades que dependen de un propvalor modificado .

Ejemplo de uso:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

Debo agradecer este artículo que me iluminó sobre la solución que decenas de otras publicaciones, blogs y repositorios no mencionaron. Cualquiera que haya tenido problemas para encontrar una respuesta a este problema evidentemente oscuro, aquí está:

Métodos del ciclo de vida de los componentes de ReactJ: un análisis profundo

componentWillReceivePropses donde se actualizará statepara mantenerse sincronizado con las propsactualizaciones.

Una vez que statelas actualizaciones, a continuación, los campos en función de state hacer volver a hacer!

SherylHohman
fuente
3
A React 16.3partir de ahora, debes usar el en getDerivedStateFromPropslugar de componentWillReceiveProps. Pero, de hecho, ni siquiera debería hacer todo eso como se articula aquí
kyw
no funciona, siempre que el estado cambia, entonces componentWillReceiveProps funcionará en muchos casos en que intento que esto no funcione. Especialmente, accesorios anidados o complejos de varios niveles, así que tengo que asignar un componente con una ID y activar el cambio y actualizar el estado para volver a renderizar nuevos datos
May Weather VN
0

Esta respuesta es un resumen del artículo de Brian Vaughn titulado Probablemente no necesite un estado derivado (7 de junio de 2018).

Derivar el estado de los accesorios es un anti-patrón en todas sus formas. Incluyendo el uso de la más antigua componentWillReceivePropsy la nueva getDerivedStateFromProps.

En lugar de derivar el estado de los accesorios, considere las siguientes soluciones.

Dos recomendaciones de mejores prácticas

Recomendación 1. Componente totalmente controlado
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Recomendación 2. Componente totalmente no controlado con clave
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Dos alternativas si, por cualquier motivo, las recomendaciones no funcionan para su situación.

Alternativa 1: restablecer el componente no controlado con un accesorio de identificación
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Alternativa 2: restablecer el componente no controlado con un método de instancia
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Déjame pensar en ello
fuente
-2

como sé, lo único que hace redux, al cambiar el estado de la tienda es llamar a componentWillRecieveProps si su componente dependía del estado mutado y luego debe forzar a su componente a actualizar, es así

1-store State change-2-call (componentWillRecieveProps (() => {cambio de estado de 3 componentes}))

ghazaleh javaheri
fuente