SetState Execution mechanism

SetState (updater, [callback])

Updater: updates data FUNCTION/OBJECT callback: callback FUNCTION after successful updateCopy the code
// updater - Function
this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

// update - Object
this.setState({quantity: 2})
Copy the code

SetState features:

1. Asynchronous: React usually aggregates a batch of components that need to be updated and then updates them all at once to ensure performance

2. Shallow merge objecr.assign ()

SetState problem and solution

  • After using setState to change the state, immediately use this.state to retrieve the latest state: componentDidUpdate or setState callback
ChangeTitle: function (event) {this.setState({title: event.target.value }, () => this.APICallFunction()); }, APICallFunction: function () { // Call API with the updated value }Copy the code
  • There is a requirement that needs to be added twice in onClick, which will only be added once for method updates using objects: use the updater function
onClick = () => { this.setState({ index: this.state.index + 1 }); this.setState({ index: this.state.index + 1 }); } // The result is that subsequent data overwrites previous changes, so it is only added once. Object.assign( previousState, {index: state.index+ 1}, {index: SetState ((prevState, props) => {this.setState((prevState, props) => {return {quantity: prevState.quantity + 1}; }); this.setState((prevState, props) => { return {quantity: prevState.quantity + 1}; }); }Copy the code

Note:


1. Do not write setstate() in render(), unless you have shouldComponentUpdate, it will cause an infinite loop

Render () {// this.setstate return (//... Dom)}Copy the code

Use setState to insert data into a queue. Use the first method that does not trigger render. React provides an instance method of setState that triggers render

// 1
this.state.num = 1
// 2
this.setState({
    num: this.state.num + 1
})
Copy the code

Array: Do not use push, pop, shift, unshift, splice. Concat, slice, filter, extension syntax object: Object.assgin/ Extended syntax

SetState update mechanism

See the pending queue and Update queue

SetState source

this.setState()

ReactComponent.prototype.setState = function (partialState, Callback) {/ / / / this. Put setState affairs in the queue updater is ReactUpdateQueue, this is the instance of a component. This updater. EnqueueSetState (this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); }};Copy the code

enqueueSetState()

enqueueSetState: function (publicInstance, PartialState) {// Get the instance of the current component. There are two very important properties in the instance :\_pendingStateQueue and \_pendingCallbacks var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState'); / / initialization to update the queue var queue = internalInstance. \ _pendingStateQueue | | (internalInstance. \ _pendingStateQueue = \ [\]); Queue. Push (partialState); // Queue. Push (partialState); // The component instance to be updated is also placed in a queue enqueueUpdate(internalInstance); }Copy the code

enqueueUpdate

If you are currently in the process of creating/updating a component, you do not update the component immediately, but put the current component in the dirtyComponent first, so not every setState updates the component

Function enqueueUpdate(Component) {// If not in the batch create/update component phase, the update state transaction if (! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // If you are in the process of batch creating/updating components, place the current component in the dirtyComponents array dirtyComponents.push(Component); }Copy the code

batchingStrategy

Var ReactDefaultBatchingStrategy = {/ / used to mark is currently out of the batch update isBatchingUpdates: false, / / when this method is called, the official start of the batch update batchedUpdates: function (callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; EnqueueUpdate if (alreadyBatchingUpdates) {return callback(a, b, c, d, e); } else {return transaction. Perform (callback, null, a, b, c, d, e); }}};Copy the code

transaction

Perform (anyMethos) -> closeCopy the code

// set isBatchingUpdates to false var RESET\_BATCHED\_UPDATES = {initialize: emptyFunction, close: function () { ReactDefaultBatchingStrategy.isBatchingUpdates = false; }}; // Loop through all dirtyComponents, call updateComponent to execute all lifecycle methods, componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, Var FLUSH\_BATCHED\_UPDATES = {initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) }; var TRANSACTION\_WRAPPERS = \[FLUSH\_BATCHED\_UPDATES, RESET\_BATCHED\_UPDATES\];Copy the code

SetState interview

Interviewer: “Is setState synchronous or asynchronous in React?” Me: “Asynchronous, setState doesn’t get results right away.” Interviewer: “In what circumstances is it asynchronous, can it be synchronous, and in what circumstances is it synchronous?”

The next result is:

class App extends React.Component { state = { val: 0 } componentDidMount() { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) this.setState({ val: this.state.val + 1 }) console.log(this.state.val) setTimeout(\_ => { this.setState({ val: this.state.val + 1 }) console.log(this.state.val); this.setState({ val: This.state. val + 1}) console.log(this.state.val)}, 0)} render() {return <div>{this.state.val}</div>}} 0, 2, 3Copy the code

conclusion

1. SetState is “asynchronous” only in synthesized events and hook functions, synchronous in native events and setTimeout. 2. 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 the updated value cannot be immediately obtained in the synthesized event and hook function, which forms the so-called “asynchronous”. Of course, the callback in the second parameter setState(partialState, callback) can get the updated result. 3. The batch update optimization of setState is also based on “asynchronous” (composite event, hook function). The batch update will not be performed in the native event and setTimeout. Take the last execution, if setState is multiple different values at the same time, it will be merged during the update batch update.

1. SetState in synthetic events

React encapsulates a set of event mechanisms to address cross-platform and compatibility issues, and proxies for native events such as onClick and onChange, which are common in JSX. Synthetic events also have batchedUpdates, which are completed through the same transaction

class App extends Component { state = { val: 0 } increment = () => { this.setState({ val: This.state. val + 1}) console.log(this.state.val) // render() {return (<div) onClick={this.increment}> {\`Counter is: ${this.state.val}\`} </div> ) } }Copy the code

2. SetState in lifecycle functions

The entire lifecycle is a transaction operation, so the identifier bit isBatchingUpdates = true, so when the process reaches enqueueUpdate(), the instance object will be added to the dirtyComponents array

class App extends Component { state = { val: 0 } componentDidMount() { this.setState({ val: This.state. val + 1}) console.log(this.state.val) // render() {return (<div> {Counter is: ${this.state.val}\`} </div> ) } }Copy the code

3. SetState in native events

Native events are non-React synthesized events. Native events listen to addeventListeners. Or you can use native JS, jQ directly document.querySelector(). Onclick and the binding event forms are native events. Native event bindings are not handled in the way of composite events, and they don’t enter the process of updating transactions. The same goes for setTimeout. When the setTimeout callback is executed, the original component update process has already been completed. The dirtyComponent is not put in for asynchronous update, and the result is naturally synchronous.

class App extends Component { state = { val: 0 } changeValue = () => { this.setState({ val: ComponentDidMount () {this.state.val + 1}) console.log(this.state.val); document.body.addEventListener('click', this.changeValue, false) } render() { return ( <div> {\`Counter is: ${this.state.val}\`} </div> ) } }Copy the code

4. SetState in setTimeout

Based on the Event loop model, setState in setTimeout can always get the latest state value.

class App extends Component { state = { val: 0 } componentDidMount() { setTimeout(\_ => { this.setState({ val: This.state. val + 1}) console.log(this.state.val) // Output updated value --> 1}, 0)} render() {return (<div> {\ 'Counter is: ${this.state.val}\`} </div> ) } }Copy the code

5. Batch update

In setState, react creates an updateQueue and maintains an updateQueue using firstUpdate, lastUpdate, lastupdate. next. In the final performWork, The same key will be overwritten, and only the last setState will be updated

class App extends Component {

  state = { val: 0 }

  batchUpdates = () => {
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
 }

  render() {
    return (
      <div onClick={this.batchUpdates}>
        {\`Counter is ${this.state.val}\`} // 1
      </div>
    )
  }
}
Copy the code

Why is setState asynchronous

1. Ensure internal consistency

Even if state is synchronous update, props is not. (You only know props when the parent component is re-rendering)

2. Performance optimization

Delaying state updates until the final batch merge before rendering can be of great benefit to the performance optimization of the application, as rerendering the real DOM for every state change can be a huge performance drain.

Principles of interpretation

Interviewer: “Is setState synchronous or asynchronous in React?” Me: “Asynchronous, setState doesn’t get results right away.”

Interviewer: “In what circumstances is it asynchronous, can it be synchronous, and in what circumstances is it synchronous?” Me: “…”

SetState is not really an asynchronous operation, it just simulates the behavior of asynchrony

Why do you say so? The following example is not a true asynchronous operation

class App extends Component { state = { count: 0 }; ComponentDidMount () {// Call this.setState({count: this.state.count + 1}); console.log("lifecycle: " + this.state.count); SetTimeout (() => {// setTimeout call this.setState({count: this.state.count + 1}); console.log("setTimeout: " + this.state.count); }, 0); document.getElementById("div2").addEventListener("click", this.increment2); } increment = () => {this.setState({count: this.state.count + 1}); console.log("react event: " + this.state.count); }; Increment2 = () => {// Call this.setState({count: this.state.count + 1}) in native event; console.log("dom event: " + this.state.count); }; render() { return ( <div className="App"> <h2>couont: {this.state.count}</h2> <div id="div1" onClick={this.increment}> click me and count+1 </div> <div id="div2">click me and  count+1</div> </div> ); }}Copy the code

React encapsulates a set of event mechanisms and proxies for native events, such as onClick and onChange, which are common in JSX.

Call setState() in the above four ways, followed by the latest state, according to the asynchronous principle mentioned before, should not be able to get. However, calls in setTimeout and native events can get the latest state immediately. The fundamental reason is that setState is not really an asynchronous operation, it just simulates asynchronous behavior. React maintains a flag (isBatchingUpdates) that determines whether to update directly or temporarily queue state first. Both setTimeout and native events update state directly, so you get the latest state immediately. The composite event and React lifecycle functions, which are controlled by React, will set isBatchingUpdates to true, thus doing something similar to asynchrony.

Going setState in setTimeout is not a separate scenario, it depends on your outer layer, because you can setTimeout in a synthesized event, you can setTimeout in a hook function, you can setTimeout in a native event, However, in any scenario, under the event loop-based model, setState in setTimeout can always get the latest state value.

Source code analysis

conclusion

  1. setStateIs “asynchronous” only in synthesized events and hook functions, in native events andsetTimeoutAll of them are synchronized.
  2. setState“Asynchronous” does not mean that the internal implementation of asynchronous code, in fact, the execution of the process and code 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, which forms the so-called “asynchronous”. Of course, the callback in the second parameter setState(partialState, callback) can get the updated result.
  3. The setStateBatch updateOptimization is also based on “asynchrony” (composite events, hook functions) between native events andsetTimeout Will not batch update,In asynchrony, if the same value is setState multiple times, the batch update policy of setState will overwrite it and take the last execution. If there are multiple different values of setState at the same time, it will be updated in a combined batch.

3 can be combined with the following example:

class App extends React.Component {
  state = { val: 0 }

  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)

    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);

      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
    }, 0)
  }

  render() {
    return <div>{this.state.val}</div>
  }
}
Copy the code

In combination with the above analysis, setState in the hook function cannot get the updated value immediately, so the first two times output 0. When executing setTimeout, the values of the first two states have been updated. Due to the setState batch update strategy, This.state. val is only valid for the last time, which is 1. In setTimmout, setState is used to get update results synchronously, so setTimeout outputs 2, 3, and the final result is 0, 0, 2, 3.

How do you execute the first two consecutive + 1s in the code above?

class App extends React.Component {
  state = { val: 0 }

  componentDidMount() {
    this.setState((preState) => {
        console.log(preState.val)   // 0
        return { val: preState + 1 }
    })
     
    this.setState((preState) => {
        console.log(preState.val)   // 1
        return { val: preState.val + 1 }
    })

    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);  // 3

      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)  // 4
    }, 0)
  }

  render() {
    return <div>{this.state.val}</div>
  }
}
Copy the code