¿React mantiene el orden de las actualizaciones de estado?


Sé que React puede realizar actualizaciones de estado de forma asíncrona y por lotes para optimizar el rendimiento. Por lo tanto, nunca se puede confiar en que el estado se actualizará después de haber llamado setState. Pero puedes confiar en React para actualizar el estado en el mismo orden que setState se llama para

  1. el mismo componente?
  2. diferentes componentes?

Considere hacer clic en el botón en los siguientes ejemplos:

1. ¿Existe alguna vez la posibilidad de que a sea falso y b es verdadero para:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. ¿Existe alguna vez la posibilidad de que a sea falsa y b sea verdadera para:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Tenga en cuenta que estas son simplificaciones extremas de mi caso de uso. Me doy cuenta de que puedo hacer esto de manera diferente, por ejemplo, actualizar ambos parámetros de estado al mismo tiempo en el ejemplo 1, así como realizar la segunda actualización de estado en una devolución de llamada a la primera actualización de estado en el ejemplo 2. Sin embargo, esta no es mi pregunta, y solo estoy interesado en si hay una forma bien definida de que React realice estas actualizaciones de estado, nada más.

Cualquier respuesta respaldada por documentación es muy apreciada.

Author: darksmurf, 2018-02-01

4 answers

Trabajo en React.

TLDR:

Pero ¿puedes confiar en React para actualizar el estado en el mismo orden en que se llama a setState para

  • el mismo componente?

Sí.

  • diferentes componentes?

Sí.

El orden de las actualizaciones siempre se respeta. Si usted ve un estado intermedio "entre" ellos o no depende de si usted está dentro en un lote o no.

Actualmente (React 16 y anteriores), solo las actualizaciones dentro de los controladores de eventos de React se agrupan por defecto. Hay una API inestable para forzar el procesamiento por lotes fuera de los controladores de eventos para casos raros cuando lo necesite.

En futuras versiones (probablemente React 17 y posteriores), React batcheará todas las actualizaciones por defecto para que no tengas que pensar en esto. Como siempre, anunciaremos cualquier cambio sobre esto en el blog React y en el lanzamiento nota.


La clave para entender esto es que no importa cuántas llamadas setState() en cuántos componentes haga dentro de un controlador de eventos de React, solo producirán un solo re-renderizado al final del evento. Esto es crucial para un buen rendimiento en aplicaciones grandes porque si Child y Parent cada llamada setState() al manejar un evento de clic, no desea volver a renderizar el Child dos veces.

En ambos ejemplos, setState() las llamadas ocurren dentro de un React controlador de eventos. Por lo tanto, siempre se enjuagan juntos al final del evento (y no se ve el estado intermedio).

Las actualizaciones son siempre fusionadas superficialmente en el orden en que ocurren. Así que si la primera actualización es {a: 10}, la segunda es {b: 20}, y la tercera es {a: 30}, el estado renderizado será {a: 30, b: 20}. La actualización más reciente a la misma clave de estado (por ejemplo, como a en mi ejemplo) siempre "gana".

El objeto this.state se actualiza cuando re-renderizamos la interfaz de usuario en el fin del lote. Por lo tanto, si necesita actualizar el estado basado en un estado anterior (como incrementar un contador), debe usar la versión funcional setState(fn) que le da el estado anterior, en lugar de leer desde this.state. Si tienes curiosidad sobre el razonamiento para esto, lo expliqué en profundidad en este comentario.


En su ejemplo, no veríamos el "estado intermedio" porque estamos dentro de un controlador de eventos de React donde el procesamiento por lotes está habilitado (porque React "sabe" cuando estamos saliendo de ese evento).

Sin embargo, tanto en React 16 como en versiones anteriores, todavía no hay procesamiento por lotes por defecto fuera de los controladores de eventos de React. Así que si en su ejemplo tuviéramos un controlador de respuesta AJAX en lugar de handleClick, cada setState() se procesaría inmediatamente a medida que sucede. En este caso, sí, verías un estado intermedio:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Nos damos cuenta de que es inconveniente que el comportamiento es diferente dependiendo de si estás en un controlador de eventos o no. Esto cambiará en una futura versión de React que procesará por lotes todas las actualizaciones de forma predeterminada (y proporcionará una API de suscripción para eliminar los cambios de forma sincrónica). Hasta que cambiemos el comportamiento predeterminado (potencialmente en React 17), hay una API que puede usar para forzar el procesamiento por lotes :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Los controladores de eventos de React internamente están empaquetados en unstable_batchedUpdates, por lo que están agrupados por defecto. Tenga en cuenta que empaquetar una actualización en unstable_batchedUpdates dos veces no tiene efecto. Las actualizaciones son sonrojado cuando salimos de la llamada unstable_batchedUpdates más externa.

Esa API es "inestable" en el sentido de que la eliminaremos cuando el procesamiento por lotes ya esté habilitado por defecto. Sin embargo, no lo eliminaremos en una versión menor, por lo que puede confiar de forma segura en él hasta React 17 si necesita forzar el procesamiento por lotes en algunos casos fuera de los controladores de eventos de React.


Para resumir, este es un tema confuso porque React solo almacena lotes dentro de los controladores de eventos por defecto. Esto cambiará en futuras versiones, y el el comportamiento será más sencillo entonces. Pero la solución no es lote menos, es lote más por defecto. Eso es lo que vamos a hacer.

 199
Author: Dan Abramov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-04 17:57:44

Esta es en realidad una pregunta bastante interesante, pero la respuesta no debería ser demasiado complicada. Hay este gran artículo sobre medium que responde a la pregunta.

1) Si haces esto

this.setState({ a: true });
this.setState({ b: true });

No creo que haya una situación donde a será true y b será false debido a por lotes.

Sin embargo, si b depende de a entonces, de hecho, podría haber una situación en la que no obtendrías el estado esperado.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Después de que se procesen todas las llamadas anteriores this.state.value será 1, no 3 como cabría esperar.

Esto se menciona en el artículo: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Esto nos dará this.state.value === 3

 3
Author: Michal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 13:22:48

Como en doc

SetState() enqueues cambia el estado del componente y le dice a React que este componente y sus hijos necesitan ser re-renderizados con el estado actualizado. Este es el método principal que utiliza para actualizar el usuario interfaz en respuesta a controladores de eventos y respuestas del servidor.

Se preforma el cambio como en la cola (FIFO : First In First Out), la primera convocatoria será el primero de preformas

 3
Author: Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 13:48:51

Se pueden agrupar varias llamadas durante el mismo ciclo. Por ejemplo, si intenta incrementar la cantidad de un artículo más de una vez en el mismo ciclo, eso resultará en el equivalente de:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

Https://reactjs.org/docs/react-component.html

 2
Author: Mosè Raguzzini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 13:21:09