Answer first: sometimes synchronous, sometimes asynchronous. SetState is asynchronous in synthesized events and lifecycle functions, and synchronous in native events and setTimeout

Synthetic events and lifecycle functions are asynchronous

We can look at one 🌰 :

export default class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        <h1>{count}</h1>
        <button id="add" onClick={this.btnChange}>+</button>
      </div>
    );
  }
  btnChange = () => {
    this.setState({
      count: this.state.count + 1,
    });
    console.log("this.state.count :>> ", this.state.count);
  };
}

Copy the code

Now, when we click on the button, the printed state is 0, not the latest one,

setState
setState
callback

BtnChange = () => {this.setState({count: this.state.count + 1,}, () => {// Now print 1 console.log()"this.state.count :>> ", this.state.count); }); // Print 0 console.log("this.state.count :>> ", this.state.count);
  };
Copy the code

It is important to note that in the console, 0 is printed first, then 1.

The same is true in the component lifecycle.

Second, in the native event andsetTimeoutIt’s in sync

In native DOM times and setTimeout, this is synchronized, and we’ll modify the click events above

  btnChange = () => {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      });
      console.log("this.state.count :>> ", this.state.count);
    }, 0);
  };
Copy the code

In this case, the updated count value is printed

DOM

  componentDidMount() {
    document.querySelector("#add").addEventListener("click", () => {
      this.setState({
        count: this.state.count + 1,
      });
      console.log("this.state.count :>> ", this.state.count);
    });
  }
Copy the code

Three, asynchronoussetStateProblems that may be merged

Also, there is a problem with asynchronous setState. When the same asynchronous setState operation is performed multiple times, it will be merged before updating.

  btnChange = () => {
    this.setState({
      count: this.state.count + 1,
    },() => {
      console.log("this.state.count :>> ", this.state.count);
    });

    this.setState({
      count: this.state.count + 1,
    },() => {
      console.log("this.state.count :>> ", this.state.count);
    });

    this.setState({
      count: this.state.count + 1,
    },() => {
      console.log("this.state.count :>> ", this.state.count);
    });

    this.setState({
      count: this.state.count + 1,
    },() => {
      console.log("this.state.count :>> ", this.state.count);
    });
  };
Copy the code

And the answer is obviously 1, 4 times

setState
setState
state
state
setState
state
state
count
state
state
state

So how do we keep it from merging?

We can return an object in setState in the form of a callback function:

  btnChange = () => {
    this.setState((state,props) => {
      return {
        count: state.count + 1,
      };
    },() => {
      console.log('this.state.count :>> ', this.state.count);
    });
    this.setState((state,props) => {
      return {
        count: state.count + 1,
      };
    },() => {
      console.log('this.state.count :>> ', this.state.count);
    });
    this.setState((state,props) => {
      return {
        count: state.count + 1,
      };
    },() => {
      console.log('this.state.count :>> ', this.state.count);
    });
    this.setState((state,props) => {
      return {
        count: state.count + 1,
      };
    },() => {
      console.log('this.state.count :>> ', this.state.count);
    });
  };
Copy the code

This callback takes two arguments, one is the current state and the other is props. On return {}, count is returned to state, setState is executed, and so on. When the asynchronous operation is complete, The callback function in setState is executed, at which point count is already the latest 4, so 4 is printed four times. Although we can’t see the result of its printing from 1 to 4, we need to be clear about this process.

In synchronous setState, no merge takes place

  btnChange = () => {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      },() => {
        console.log("this.state.count :>> ", this.state.count);
      });
    });
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      },() => {
        console.log("this.state.count :>> ", this.state.count);
      });
    });
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      },() => {
        console.log("this.state.count :>> ", this.state.count);
      });
    });
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      },() => {
        console.log("this.state.count :>> ", this.state.count);
      });
    });
  };
Copy the code

We can see that the printed results are as expected:

Four, conclusion

Finally, I will talk about the reason why React does not allow direct modification of state. Remember PureComponent and shouldComponentUpdate, which are often used for performance optimizations. Its purpose is to intercept component rendering. Shallow comparison of the previous state and props to the updated state and props to determine whether to render the component. If we change the value of state directly, the comparison is meaningless and remains the same, and there is no way to optimize performance. Not to mention React won’t re-render at all if the value of state stays the same.

Because the author is still a front end small sprout new, unavoidably can have careless mistake, welcome big guys to give advice!