Why is setState in reactjs Async instead of Sync?

后端 未结 7 1399
一生所求
一生所求 2020-11-21 22:58

I have just found that in react this.setState() function in any component is asynchronous or is called after the completion of the function that it was called i

7条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2020-11-21 23:19

    Imagine incrementing a counter in some component:

      class SomeComponent extends Component{
    
        state = {
          updatedByDiv: '',
          updatedByBtn: '',
          counter: 0
        }
    
        divCountHandler = () => {
          this.setState({
            updatedByDiv: 'Div',
            counter: this.state.counter + 1
          });
          console.log('divCountHandler executed');
        }
    
        btnCountHandler = () => {
          this.setState({
            updatedByBtn: 'Button',
            counter: this.state.counter + 1
          });
          console.log('btnCountHandler executed');
        }
        ...
        ...
        render(){
          return (
            ...
            // a parent div
            
    // a child button
    ... ) } }

    There is a count handler attached to both the parent and the child components. This is done purposely so we can execute the setState() twice within the same click event bubbling context, but from within 2 different handlers.

    As we would imagine, a single click event on the button would now trigger both these handlers since the event bubbles from target to the outermost container during the bubbling phase.

    Therefore the btnCountHandler() executes first, expected to increment the count to 1 and then the divCountHandler() executes, expected to increment the count to 2.

    However the count only increments to 1 as you can inspect in React Developer tools.

    This proves that react

    • queues all the setState calls

    • comes back to this queue after executing the last method in the context(the divCountHandler in this case)

    • merges all the object mutations happening within multiple setState calls in the same context(all method calls within a single event phase is same context for e.g.) into one single object mutation syntax (merging makes sense because this is why we can update the state properties independently in setState() in the first place)

    • and passes it into one single setState() to prevent re-rendering due to multiple setState() calls (this is a very primitive description of batching).

    Resultant code run by react:

    this.setState({
      updatedByDiv: 'Div',
      updatedByBtn: 'Button',
      counter: this.state.counter + 1
    })
    

    To stop this behaviour, instead of passing objects as arguments to the setState method, callbacks are passed.

        divCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByDiv: 'Div',
                  counter: prevState.counter + 1
                };
              });
              console.log('divCountHandler executed');
            }
    
        btnCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByBtn: 'Button',
                  counter: prevState.counter + 1
                };
              });
          console.log('btnCountHandler executed');
        }
    

    After the last method finishes execution and when react returns to process the setState queue, it simply calls the callback for each setState queued, passing in the previous component state.

    This way react ensures that the last callback in the queue gets to update the state that all of its previous counterparts have laid hands on.

提交回复
热议问题