[React Deeper] setState’s Execution Mechanism

  Front end, javascript, react.js, Source code

I. Several Problems Frequently Encountered in Development

The following problems are the scenarios we often encounter in actual development. Here are some simple sample codes to restore them.

1. Is 1.setState synchronous or asynchronous, why can’t you get the update results immediately sometimes and sometimes?

1.1 in the hook function and React synthesis eventsetState

There are now two components

componentDidMount() {
 console.log('parent componentDidMount');
 }
 
 render() {
 return (
 <div>
 <SetState2></SetState2>
 <SetState></SetState>
 </div>
 );
 }

Put the same code inside the component andSetstate1hit the targetcomponentDidMountPut a synchronous delay code in the, print delay time:

componentWillUpdate() {
 console.log('componentWillUpdate');
 }
 
 componentDidUpdate() {
 console.log('componentDidUpdate');
 }
 
 componentDidMount() {
 Log ('setState console.log('SetState');
 this.setState({
 index: this.state.index + 1
 })
 console.log('state', this.state.index);
 
 Log ('setState console.log('SetState');
 this.setState({
 index: this.state.index + 1
 })
 console.log('state', this.state.index);
 }

The following are the results of the implementation:

image

Description:

  • 1. CallsetStateNot updated immediately
  • 2. All components use the same update mechanism. When all componentsdidmountAfter, the parent componentdidmount, and then perform the update
  • 3. When updating, the updates of each component will be merged, and each component will trigger the life cycle of the update only once.

1.2 in asynchronous functions and native eventssetstate?

InsetTimeoutCall insetState(The example has the same effect as executing in browser native events and interface callbacks)

componentDidMount() {
 setTimeout(() => {
 Log ('call setState');
 this.setState({
 index: this.state.index + 1
 })
 console.log('state', this.state.index);
 Log ('call setState');
 this.setState({
 index: this.state.index + 1
 })
 console.log('state', this.state.index);
 }, 0);
 }

Results of implementation:

image

Description:

  • 1. In the parent componentdidmountPost-execution
  • 2. CallsetStateSynchronous update

2. Why is it sometimes twice in a rowsetStateOnly once?

Execute the following code separately:

componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
componentDidMount() {
this.setState((preState) => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
this.setState(preState => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
}

Results of implementation:

1
 1
2
 2

Description:

  • 1. Direct delivery of objectssetstateWill be merged into one
  • 2. Use function transferstateWill not be merged

SetState implementation process

Due to the complexity of the source code, it will not be posted here. Those who are interested can go there.githuboncloneOne copy and follow the flow chart below.

1. Flow chart

setState

The picture is not clear, you can clickView original image

  • partialStatesetStateThe first parameter, object, or function passed in
  • _pendingStateQueue: the current component is waiting to perform an updatestateQueue
  • isBatchingUpdates: react is used to identify whether it is currently in batch update status. All components are common
  • dirtyComponent: currently all component queues in the pending update state
  • transcation: react’s transaction mechanism wraps N methods called by the transaction.waperObject and execute it once:waper.init, called method,waper.close
  • FLUSH_BATCHED_UPDATES: for performing updateswaperThere is only onecloseMethod

2. Implementation process

According to the text description of the above flowchart, it can be roughly divided into the following steps:

  • 1. passed setState inpartialStateParameters are stored in the state staging queue of the current component instance.
  • 2. Judge whether the current React is in a batch update state, if so, add the current component to the component queue to be updated.
  • 3. If it is not in the batch update status, set the batch update status flag to true, and use the transaction to call the previous method again to ensure that the current component is added to the queue of components to be updated.
  • 4. Invoking the transactionwaperMethod to traverse the queue of components to be updated to perform updates in turn.
  • 5. Implementation life cyclecomponentWillReceiveProps.
  • 6. Temporarily store the state of the component in the queuestateMerge to get the final state object to update and leave the queue empty.
  • 7. Implementation life cyclecomponentShouldUpdate, according to the return value to determine whether to continue to update.
  • 8. Implementation life cyclecomponentWillUpdate.
  • 9. Implement real updates,render.
  • 10. Implementation life cyclecomponentDidUpdate.

III. Summary

1. Hook function and composite event:

InreactIn the life cycle and composite events of the,reactIs still in his update mechanism, at this timeisBranchUpdateTrue.

According to the above procedure, no matter how many times it is calledsetState, will not perform the update, but will be updatedstateDeposit_pendingStateQueueTo store the components to be updateddirtyComponent.

When the last update mechanism was completed, take the life cycle as an example, all components, that is, the top-most componentdidmountLater, it willisBranchUpdateSet to false. At this time, the previously accumulated will be executedsetState.

2. Asynchronous functions and native events

Judging from the implementation mechanism,setStateIt is not asynchronous in itself, but if you callsetStateWhen, ifreactIn the process of updating, the current update will be temporarily stored and executed after the last update is executed. This process gives the illusion of asynchrony.

In the life cycle, according to the asynchronous mechanism of JS, asynchronous functions will be temporarily stored first and executed after all synchronous codes have been executed. At this time, the last update process has been completed.isBranchUpdateIs set to false, according to the above process, then callsetStateYou can immediately execute the update and get the update results.

3.partialStateMerger mechanism

Let’s look at the process_processPendingStateThis function is used to mergestateTemporary queue, the last to return a mergedstate.

_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;

if (!  queue) {
return inst.state;
}

if (replace && queue.length === 1) {
return queue[0];
}

var nextState = _assign({}, replace ?   queue[0] : inst.state);
for (var i = replace ?   1 : 0;   i < queue.length;  i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ?   partial.call(inst, nextState, props, context) : partial);
}

return nextState;
},

We just need to focus on the following code:

_assign(nextState, typeof partial === 'function' ?   partial.call(inst, nextState, props, context) : partial);

If an object is passed in, it will obviously be merged into one:

Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)

If a function is passed in, the parameter preState of the function is the result of the previous merge, so the calculation result is accurate.

4.componentDidMountcallsetstate

In componentDidMount (), you can call setState () immediately. It will trigger an additional rendering, but it will occur before the browser refreshes the screen. This ensures that even if render () will be called twice in this case, the user will not see the intermediate state. Use this mode cautiously because it often leads to performance problems. In most cases, you can use the initial state of assignment instead in the constructor (). However, this is necessary in some cases, such as modal boxes and tooltip boxes. At this point, you need to measure these DOM nodes before rendering something that depends on size or location.

The above is the description of official documents, not recommended directly incomponentDidMountDirect callsetStateFrom the above analysis:componentDidMountWe are in the middle of an update and we called it again.setState, it will be done again in the future.render, causing unnecessary waste of performance, most cases can be done by setting the initial value.

Of course it iscomponentDidMountWe can call the interface and modify it in the callback.stateThis is the right thing to do.

When the state initial value depends on the dom attribute, thecomponentDidMountInsetStateIt is inevitable.

5.componentWillUpdate componentDidUpdate

Cannot be called in these two life cyclessetState.

It is easy to find from the above flow chart, call in themsetStateIt will cause a dead loop and cause the program to crash.

6. Recommended Usage

On callsetStateWhen using function transferstateValue to get the latest updated in the callback functionstate.