This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together. details

SetState Asynchronous update

As we all know, React accesses state through this.state and updates state through the this.setstate () method. When the this.setstate () method is called, React will re-call the Render method to re-render the UI

So is setState asynchronous at all times?

First of all, you can’t get the value of state directly after setState. Where React internals can detect it, setState is asynchronous; Where no React detection, for example AddEventListener native events, setInterval and setTimeout, setState is an update

➤ Is it synchronous or asynchronous?

SetState is not purely asynchronous or synchronous, it actually depends on the context in which it is called

  • inSynthetic eventsLifecycle hook(in addition tocomponentDidUpdate),setStateIt’s “asynchronous”;
  • inNative eventssetTimeout,setStateIs synchronous, can immediately get the updated value;
  • Batch update: multiple sequentialsetStateInstead of executing drops one by one synchronously, they are queued one by one and then executed together at the end. In composite events and lifecycle hooks, setState updates the queue by storing the merge state (object.assign). So the previous key is overwritten and only one update is performed.
  • Functional:SetState the first parameter isFunctional formIn this function, you can call back to get the latest state object, and the object returned by this function will be set to newState.this.setState((state, props) => newState)

Asynchronous?

The “asynchronous” of setState does not mean that it is internally realized by asynchronous code, in fact, the process and code itself are synchronous, but the call order of the synthesized event and hook function is before the update, so that the updated value cannot be immediately obtained in the synthesized event and hook function, forming the so-called “asynchronous”. Of course, the callback in the second parameter setState(partialState, callback) can get the updated result

Batch update:

Batch update optimization for setState is also based on “asynchrony” (composite events, hook functions) and does not batch update in native events and setTimeout. (1) In asynchronous mode, if the same value is setState multiple times, the setState batch update policy will overwrite it and take the last execution. (2) If multiple different values are setState at the same time, the batch update will be combined.

Why do setState” asynchronize “batch processing?

  • The asynchronous design was designed to optimize performance and reduce the number of renders
  • Maintain internal consistency. If you change state to synchronous update, though the update to state is synchronous, the props is not
  • Enable concurrent updates to complete asynchronous rendering.

SetState principle

SetState is not really asynchronous, it just looks asynchronous. In the source code, check by isBatchingUpdates

SetState call flow:

Call this.setState(newState) -> store the newState in the pending queue -> check whether the batch Update (isBatchingUpdates whether true) -> ④isBatchingUpdates=true, save components in dirtyComponents, go asynchronous update process, merge operation, delay update;

⑤isBatchingUpdates=false, take the synchronization process. Iterate through all the dirtyComponents and call updateComponent to update the pending state or props

SetState Batch update process

There are corresponding hooks before and after the React life cycle and the execution of the composite event, namely the Pre hook and post hook

  1. The Pre hook calls the batchedUpdate method to set the isBatchingUpdates variable to true, indicating that the status is now in the update phase. Enable batch update.

    The setState update will be queued, and the state update in the queue will be executed after the synchronization code completes. If isBatchingUpdates are true, place the current component (the one that called setState) in the dirtyComponents array; Otherwise batchUpdate all queue updates

  1. whilepostThe hook willisBatchingUpdatesSet tofalse

Why does it not work to modify this.state directly

  • setStateThis is essentially implemented through a queue mechanismstateThe updated. performsetState, the state that needs to be updated will be merged into the status queue instead of being updated immediatelystate, the queuing mechanism can be batch updatedstate.
  • If you don’t passsetStateAnd modify it directlythis.stateSo thisstateIt’s not going to be put in the status queue, next callsetStateWhen the status queue is merged, the previously modified status queue is ignoredstateSo we can’t merge and we don’t actually get what you wantstateUpdate on

What happens after setState

After setState is called, React will go to the diff state, and if the state changes then it will go to the DIff DOM to determine whether to update the UI. If you go through these processes every time you setState you might have a performance problem.

React pushes state changes onto the stack when setState is set multiple times in a short period of time, and updates states and views in batches at appropriate times to improve performance.

SetState loop call risk

  • When callingsetStateIs actually executedenqueueSetStateMethods, and onpartialStateAs well as_pending-StateQueueUpdate queue for merge operation and finally passenqueueUpdateperformstateupdate
  • whileperformUpdateIfNecessaryMethods will be givenTake _pendingElement._pendingStateQueue._pending-ForceUpdateAnd callreceiveComponentandupdateComponentMethod for component updates
  • If theshouldComponentUpdateorcomponentWillUpdateMethod callsetStateAt this time,this._pending-StateQueue ! = null, causing a loop that causes the browser to crash after running out of memory

Judge state output

Look at the first example: synchronous asynchrony for setState:

class Test extends React.Component {
  state  = {
      val: 0
  };
​
  componentDidMount() {
    this.setState({ val: this.state.val + 1 });
    console.log(this.state.val); / / 0
    setTimeout(() = > {
      this.setState({ val: this.state.val + 1 });
      console.log("setTimeout: " + this.state.val);  / / 2
    }, 0);
  }
​
 
  render() {
    return null; }};Copy the code

The output is 0 2. Process analysis:

SetState (val+1) is asynchronous and val+1 does not take effect immediately. So the first log output below will not get the latest value, it will get the previous value, and it will print 0

Val = 1; val = 1; val = 2; val = 1; val = 2; val = 2

Here’s another example: batch update of setState in asynchrony:

  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    / / 0
​
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    / / 0
​
    setTimeout(() = > {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  2 / / output
​
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  3 / / output
    }, 0);
  }
Copy the code

The output is 0, 0, 2, 3. Process analysis:

The first two outputs are directly in the life cycle of the output, so it is asynchronous process, can not get the latest value, the output is 0;

Val +1 = val+1; val+1 = val+1; val+1 = val+1

(3) In setTimeout, the latest val value is 1, and setState is synchronized. After executing the first val+1 in setTimeout, val=2, and the third log output is 2.

(4) The second setState in setTimeout is the same as that in the synchronization process, the log output after val+1=3 is the latest value 3

The second value of setState:

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

The output is 1, 1. Did you guess right? Take a look at the process parsing:

(1) In the life cycle, setState is asynchronous. If you set the key value of the same state, the operation will be overwritten, equivalent to only executing count+1 once

② We know that the second parameter of setState is the callback after the update is completed, which can get the latest state.

③ In setState, these callbacks will inject the operation function into the queue first, and then execute these callbacks one by one after the batch update of state is completed. In fact, both callbacks are executed sequentially after batch updates, and count is 1, so both are output 1.

(4) Therefore, it can be understood that only one setState is left after the two setStates are combined together and covered. Their respective callbacks are executed together, so they both print 1

If we use preState:

  componentDidMount() {
    this.setState(
      preState= > ({
        count: preState.count + 1
      }), () = > {
        console.log(this.state.count) / / 2
      })
    this.setState(
      preState= > ({
        count: preState.count + 1
      }), () = > {
        console.log(this.state.count) / / 2})}Copy the code

The output is 2, 2. We know that the first argument to setState can either be an object representing newState directly, or it can be a callback function that gets the last state and then returns a newState object through the operation. So the execution flow is:

SetState = 1; setState = 1; setState = 1

The count in the preState of the callback argument is 1, and the +1 operation becomes 2

③ The second setState callback argument is executed simultaneously, and the output result is the latest count of 2

The reason is again due to batch update. If the first parameter of setState is an Object, store its merge state (object.assign). As a result, the previous key is overwritten, and multiple changes to the same key result in only one update. When the first argument is a function, the above two setState operations will store two functions in the queue. Object.assign (count +1); assign (count +1); assign (count +1); Assign the key count of the object. assign state is set to 2.

Summary of asynchronous processes

  • throughsetStateTo updatethis.stateDo not operate directlythis.stateThink of it as immutable
  • callsetStateupdatethis.stateIt’s not immediate, it’s asynchronous, so don’t assume it’s donesetStateafterthis.stateThat’s the latest value
  • Multiple sequentially executedsetStateInstead of executing the drops synchronously one by one, they are queued one by one and then finally executed together, i.e., batch processing