¿React mantiene el pedido de actualizaciones de estado?

139

Sé que React puede realizar actualizaciones de estado de forma asincrónica y por lotes para la optimización del rendimiento. Por lo tanto, nunca puede confiar en el estado que se actualizará después de haber llamado setState. Pero se puede confiar Reaccionar a actualizar el estado en el mismo orden que setStatese llama de

  1. el mismo componente?
  2. diferentes componentes?

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

1. ¿Existe alguna posibilidad de que a sea falso yb sea 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 posibilidad de que a sea falso yb sea verdadero 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 me interesa 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.

darksmurf
fuente
3
no parece una pregunta sin sentido, también puede hacer esa pregunta sobre cuestiones de github de la página de reacción, dan abramov generalmente es bastante útil allí. Cuando tenía preguntas tan difíciles que hacía y él respondía. Lo malo es que ese tipo de problemas no se comparten públicamente como en los documentos oficiales (para que otros también puedan acceder a él fácilmente). También siento que los documentos oficiales de React carecen de una amplia cobertura de algunos temas, como el tema de su pregunta, etc.
giorgim
Por ejemplo, tome esto: github.com/facebook/react/issues/11793 , creo que las cosas discutidas en ese número serían útiles para muchos desarrolladores, pero esas cosas no están en los documentos oficiales, porque la gente de FB considera eso avanzado. Lo mismo se trata de otras cosas posiblemente. Creo que un artículo oficial titulado algo como 'gestión estatal en reaccionar en profundidad' o 'trampas de gestión estatal' que explora todos los casos esquimales de gestión estatal como en su pregunta no sería malo. tal vez podamos obligar a los desarrolladores de FB a extender la documentación con tales cosas :)
giorgim
Hay un enlace a un gran artículo sobre el medio en mi pregunta. Debe cubrir el 95% de los casos de uso estatales. :)
Michal
2
@Michal, pero ese artículo aún no responde a esta pregunta en mi humilde opinión
Giorgim

Respuestas:

335

Yo trabajo en React.

TLDR:

Pero, ¿puede confiar en React para actualizar el estado en el mismo orden en que se solicita setState?

  • el mismo componente?

Si.

  • diferentes componentes?

Si.

El orden de las actualizaciones siempre se respeta. Si ves un estado intermedio "entre" o no depende de si estás dentro de un lote o no.

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

En futuras versiones (probablemente React 17 y posteriores), React agrupará todas las actualizaciones por defecto para que no tenga que pensar en esto. Como siempre, anunciaremos cualquier cambio al respecto en el blog React y en las notas de la versión.


La clave para entender esto es que no importa cuántas setState()llamadas en cuántos componentes realice dentro de un controlador de eventos React , producirán solo una nueva representación al final del evento . Esto es crucial para un buen rendimiento en aplicaciones grandes porque si Childy Parentcada llamada setState()al manejar un evento de clic, no desea volver a renderizar el Childdoble.

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

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

El this.stateobjeto se actualiza cuando volvemos a representar la interfaz de usuario al final del lote. Entonces, si necesita actualizar el estado en función de un estado anterior (como incrementar un contador), debe usar la setState(fn)versión funcional que le proporciona el estado anterior, en lugar de leer this.state. Si tienes curiosidad sobre el razonamiento de 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 React donde se habilita el procesamiento por lotes (porque React "sabe" cuando salimos de ese evento).

Sin embargo, tanto en React 16 como en versiones anteriores, todavía no hay lotes por defecto fuera de los controladores de eventos React . Entonces, si en su ejemplo tuviéramos un controlador de respuesta AJAX en lugar de handleClick, cada uno setState()se procesaría de inmediato cuando ocurriera. En este caso, sí, usted podría ver 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
});

Sabemos que es inconveniente que el comportamiento sea diferente dependiendo de si estás en un controlador de eventos o no . Esto cambiará en una futura versión de React que agrupará todas las actualizaciones por defecto (y proporcionará una API opcional para eliminar los cambios sincrónicamente). 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 Reaccionar internamente se están envolviendo, por unstable_batchedUpdateslo que están agrupados por defecto. Tenga en cuenta que ajustar una actualización unstable_batchedUpdatesdos veces no tiene ningún efecto. Las actualizaciones se vacían cuando salimos de la unstable_batchedUpdatesllamada más externa .

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


En resumen, este es un tema confuso porque React solo se agrupa dentro de los controladores de eventos de forma predeterminada. Esto cambiará en futuras versiones, y el comportamiento será más directo que entonces. Pero la solución no es agrupar menos , sino agrupar más por defecto. Eso es lo que vamos a hacer.

Dan Abramov
fuente
1
Una forma de "obtener siempre el orden correcto" es crear un objeto temporal, asignar los diferentes valores (p obj.a = true; obj.b = true. Ej. ) Y, al final, simplemente hacer this.setState(obj). Esto es seguro independientemente de si está dentro de un controlador de eventos o no. Podría ser un buen truco si a menudo te encuentras cometiendo el error de establecer el estado varias veces fuera de los controladores de eventos.
Chris
Por lo tanto, no podemos confiar en que el procesamiento por lotes se limite a un solo controlador de eventos, como lo dejó claro, al menos porque este no será el caso pronto. Entonces se supone que debemos usar setState con la función de actualización para obtener acceso al estado más reciente, ¿verdad? Pero, ¿qué sucede si necesito usar algún state.filter para hacer un XHR para leer algunos datos y luego ponerlos en estado? Parece que tendré que poner un XHR con devolución de llamada diferida (y, por lo tanto, un efecto secundario) en un actualizador. ¿Es eso considerado una mejor práctica, entonces?
Maksim Gumerov
1
Y, por cierto, eso también significa que no debemos leer de este estado. la única forma razonable de leer algún estado. X es leerlo en la función de actualización, desde su argumento. Y escribir a this.state tampoco es seguro. Entonces, ¿por qué permitir el acceso a this.state? Tal vez deberían hacer preguntas independientes, pero sobre todo estoy tratando de entender si obtuve la explicación correcta.
Maksim Gumerov
10
esta respuesta debe agregarse a la documentación de reactjs.org
Deen John
2
¿Podría aclarar en esta publicación si el "controlador de eventos React" incluye componentDidUpdatey otras devoluciones de llamada del ciclo de vida a partir de React 16? ¡Gracias por adelantado!
Ivan
6

Esta es realmente una pregunta bastante interesante, pero la respuesta no debería ser demasiado complicada. Existe este gran artículo sobre medio que tiene una respuesta.

1) Si haces esto

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

No creo que haya una situación en la que ase deba truey bse deba falseal procesamiento por lotes .

Sin embargo, si bdepende de,a entonces 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.valueserán 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

Michal
fuente
¿Qué pasa si this.state.valuese actualiza tanto en los controladores de eventos (donde setStateestá agrupado) como en las devoluciones de llamada AJAX (donde nosetState está agrupado)? En los controladores de eventos, usaría el para siempre estar seguro de que actualizo el estado usando el estado actualizado actualmente proporcionado por la función. ¿Debo usar una función de actualización dentro del código de la devolución de llamada AJAX incluso si sé que no está en lote? ¿Podría aclarar el uso de una devolución de llamada AJAX con o sin el uso de una función de actualización? ¡Gracias! updater functionsetStatesetState
tonix
@Michal, hola Michal solo quería hacer una simple pregunta, ¿es cierto que si tenemos this.setState ({value: 0}); this.setState ({valor: this.state.value + 1}); se ignorará el primer setState y solo se ejecutará el segundo setState?
Dickens
@Dickens Creo que ambos setStateserían ejecutados pero el último ganaría.
Michal
3

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

Mosè Raguzzini
fuente
3

como en doc

setState () pone en cola los cambios en el estado del componente y le dice a React que este componente y sus elementos secundarios deben volver a representarse con el estado actualizado. Este es el método principal que utiliza para actualizar la interfaz de usuario en respuesta a los controladores de eventos y las respuestas del servidor.

preformará el cambio como en la cola ( FIFO : Primero en entrar, primero en salir) la primera llamada será la primera en preformarse

Ali
fuente
hola Ali, solo quería hacer una mera pregunta, ¿es cierto que si tenemos this.setState ({value: 0}); this.setState ({valor: this.state.value + 1}); se ignorará el primer setState y solo se ejecutará el segundo setState?
Dickens