¿Qué sucede cuando se usa this.setState varias veces en el componente React?

101

Quería verificar qué sucede cuando usa this.setState varias veces (2 veces por el bien de la discusión). Pensé que el componente se procesará dos veces, pero aparentemente solo se procesará una vez. Otra expectativa que tenía era que tal vez la segunda llamada para setState se ejecutará sobre la primera, pero lo adivinó, funcionó bien.

Enlace a un JSfiddle

var Hello = React.createClass({
  render: function() {
    return (
      <div>
        <div>Hello {this.props.name}</div>
        <CheckBox />
      </div>
    );
  }
});

var CheckBox = React.createClass({
  getInitialState: function() {
    return {
      alex: 0
    };
  },

  handleChange: function(event) {
    this.setState({
      value: event.target.value
    });
    this.setState({
      alex: 5
    });
  },

  render: function() {
    alert('render');
    return (
      <div>
        <label htmlFor="alex">Alex</label>
        <input type="checkbox" onChange={this.handleChange} name="alex" />
        <div>{this.state.alex}</div>
      </div>
    );
  }
});

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

Como verá, una alerta que dice "render" aparece en cada render.

¿Tiene una explicación de por qué funcionó correctamente?

alexunder
fuente
2
React es bastante inteligente y solo se volverá a renderizar cuando se cambie el estado requerido para renderizar. En su método de renderizado que solo está usando this.state.alex, ¿qué sucede si agrega un elemento que también depende this.state.value?
Martin Wedvich
1
@MartinWedvich Se romperá en caso de que dependa de él. Para eso, tiene el método 'getInitialState': para establecer los valores predeterminados para que su aplicación no se rompa.
alexunder
1
relacionado y útil: stackoverflow.com/questions/48563650/…
andydavies

Respuestas:

116

Reaccionar lotes de actualizaciones de estado que ocurren en controladores de eventos y métodos de ciclo de vida. Por lo tanto, si actualiza el estado varias veces en un <div onClick />controlador, React esperará a que finalice el manejo de eventos antes de volver a renderizar.

Para ser claros, esto solo funciona en manejadores de eventos sintéticos controlados por React y métodos de ciclo de vida. Las actualizaciones de estado no se almacenan por lotes en AJAX ni en los setTimeoutcontroladores de eventos, por ejemplo.

Chris Gaudreau
fuente
1
Gracias, tiene sentido, ¿alguna idea de cómo se hace entre bastidores?
alexunder
13
Dado que React controla completamente los manejadores de eventos sintéticos (como <div onClick />) y los métodos del ciclo de vida de los componentes, sabe que puede cambiar de manera segura el comportamiento setStatedurante la duración de la llamada a las actualizaciones de estado por lotes y esperar a que se vacíen. En AJAX o setTimeoutmanejador, por el contrario, React no tiene forma de saber cuando nuestro manejador ha terminado de ejecutarse. Técnicamente, React pone en cola las actualizaciones de estado en ambos casos, pero se vacía inmediatamente fuera de un controlador controlado por React.
Chris Gaudreau
1
@neurosnap No creo que los documentos entren en muchos detalles sobre esto. Se menciona de forma abstracta en la sección Estado y ciclo de vida y en los documentos de setState . Consulte el código de ReactDefaultBatchingStrategy , que actualmente es la única estrategia oficial de procesamiento por lotes.
Chris Gaudreau
2
@BenjaminToueg Creo que puede usar ReactDOM.unstable_batchedUpdates(function)para lotes setStatefuera de los controladores de eventos React. Como su nombre lo indica, anulará la garantía.
Chris Gaudreau
1
Siempre es bueno referirse a este tipo twitter.com/dan_abramov/status/887963264335872000?lang=en
Hertzel Guinness
33

El método setState () no actualiza inmediatamente el estado del componente, simplemente coloca la actualización en una cola para ser procesada más tarde. React puede agrupar varias solicitudes de actualización juntas para hacer que la renderización sea más eficiente. Debido a esto, se deben tomar precauciones especiales cuando intente actualizar el estado en función del estado anterior del componente.

Por ejemplo, el siguiente código solo incrementará el atributo de valor de estado en 1 aunque se haya llamado 4 veces:

 class Counter extends React.Component{
   constructor(props){
     super(props)
    //initial state set up
     this.state = {value:0}
   }
   componentDidMount(){
    //updating state
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
   }
   render(){
    return <div>Message:{this.state.value}</div>
   }
}

Para usar un estado después de que se haya actualizado, realice toda la lógica en el argumento de devolución de llamada:

//this.state.count is originally 0
this.setState({count:42}, () => {
  console.log(this.state.count)
//outputs 42
})

El método setState (actualizador, [devolución de llamada]) puede tomar una función de actualización como su primer argumento para actualizar el estado en función del estado y las propiedades anteriores. El valor de retorno de la función de actualización se fusionará superficialmente con el estado del componente anterior. El método actualiza el estado de forma asincrónica, por lo que hay una opción de devolución de llamada que se llamará una vez que el estado haya terminado de actualizarse por completo.

Ejemplo:

this.setState((prevState, props) => { 
return {attribute:"value"}
})

A continuación, se muestra un ejemplo de cómo actualizar el estado en función del estado anterior:

    class Counter extends React.Component{
      constructor(props) {
        super(props)
    //initial state set up
        this.state = {message:"initial message"}
    }
      componentDidMount() {
    //updating state
        this.setState((prevState, props) => {
          return {message: prevState.message + '!'}
        })
     }
     render(){
       return <div>Message:{this.state.message}</div>
     }
  }
Biboswan
fuente
Ah, no sabía sobre el setState(updater,[callback]), es como un Object.assignagradecimiento menos detallado por eso!
AJ Venturella
1
@Biboswan, hola, soy nuevo en React y quería preguntarle si es cierto que los cuatro setStates dentro del método CompountDidMount están fusionados y SOLO se ejecutará EL ÚLTIMO setState, pero los otros 3 serán ignorados.
Dickens