O que acontece ao usar this.setState várias vezes no componente React?

101

Eu queria verificar o que acontece quando você usa this.setState várias vezes (2 vezes para fins de discussão). Achei que o componente seria renderizado duas vezes, mas aparentemente só foi renderizado uma vez. Outra expectativa que eu tinha era que talvez a segunda chamada para setState superasse a primeira, mas você adivinhou - funcionou bem.

Link para um 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 você verá, um alerta que diz 'render' aparece em cada render.

Você tem uma explicação de por que funcionou corretamente?

alexunder
fonte
2
O React é muito inteligente e só será renderizado novamente quando o estado necessário para renderizar for alterado. Em seu método de renderização, você está apenas usando this.state.alex- o que acontece se você adicionar um elemento que dependa this.state.valuetambém?
Martin Wedvich
1
@MartinWedvich Vai quebrar caso eu seja dependente. Para isso, você tem o método 'getInitialState' - para definir os valores padrão para que seu aplicativo não quebre.
alexunder
1
relacionado e útil: stackoverflow.com/questions/48563650/…
andydavies

Respostas:

116

Atualizações de estado dos lotes React que ocorrem em manipuladores de eventos e métodos de ciclo de vida. Portanto, se você atualizar o estado várias vezes em um <div onClick />manipulador, o React aguardará que o tratamento de eventos termine antes de renderizar novamente.

Para ficar claro, isso só funciona em manipuladores de eventos sintéticos controlados pelo React e métodos de ciclo de vida. As atualizações de estado não são agrupadas em AJAX e setTimeoutmanipuladores de eventos, por exemplo.

Chris Gaudreau
fonte
1
Obrigado, faz sentido, alguma ideia de como isso é feito nos bastidores?
alexunder
13
Como o React controla totalmente manipuladores de eventos sintéticos (como <div onClick />) e métodos de ciclo de vida de componentes, ele sabe que pode alterar com segurança o comportamento de setStatedurante a chamada para atualizações de estado em lote e esperar para liberar. Em um AJAX ou setTimeoutmanipulador, por outro lado, o React não tem como saber quando nosso manipulador terminou de executar. Tecnicamente, o React enfileira atualizações de estado em ambos os casos, mas libera imediatamente fora de um manipulador controlado pelo React.
Chris Gaudreau
1
@neurosnap Não creio que os documentos entrem em muitos detalhes sobre isso. Ele é mencionado de forma abstrata na seção State and Lifecycle e nos documentos setState . Veja o código para ReactDefaultBatchingStrategy , que atualmente é a única estratégia oficial de envio em lote .
Chris Gaudreau
2
@BenjaminToueg Acredito que você possa usar ReactDOM.unstable_batchedUpdates(function)batch setStatefora dos manipuladores de eventos React. Como o nome indica, você anulará sua garantia.
Chris Gaudreau
1
sempre bom
referir-se
33

O método setState () não atualiza imediatamente o estado do componente, ele apenas coloca a atualização em uma fila para ser processada posteriormente. O React pode agrupar várias solicitações de atualização para tornar a renderização mais eficiente. Devido a isso, precauções especiais devem ser tomadas ao tentar atualizar o estado com base no estado anterior do componente.

Por exemplo, o código a seguir apenas incrementará o atributo de valor de estado em 1, embora tenha sido chamado 4 vezes:

 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 um estado após ter sido atualizado, faça toda a lógica no argumento de retorno de chamada:

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

O método setState (atualizador, [retorno de chamada]) pode assumir uma função atualizador como seu primeiro argumento para atualizar o estado com base no estado anterior e nas propriedades. O valor de retorno da função atualizadora será mesclado superficialmente com o estado anterior do componente. O método atualiza o estado de forma assíncrona, portanto, há um retorno de chamada de opção que será chamado assim que a atualização do estado for concluída.

Exemplo:

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

Aqui está um exemplo de como atualizar o estado com base no 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
fonte
Ah, não sabia sobre o setState(updater,[callback]), é meio que um Object.assignobrigado menos prolixo por isso!
AJ Venturella de
1
@Biboswan, oi, sou novo no React e gostaria de perguntar se é verdade que todos os quatro setStates dentro do método CompountDidMount são mesclados e SOMENTE O ÚLTIMO setState será executado, mas os outros 3 serão ignorados?
Dickens