Did you really use React's setState correctly?

Keywords: Javascript Front-end React

 

  First of all, we need to make it clear that setState is not an asynchronous method, which is very similar to the reason for asynchrony: there is a deviation between the call order of hook () and synthetic events and the execution order of setState, resulting in an asynchronous presentation form, resulting in the illusion of asynchrony. Recording setState must be executed in each life cycle. Therefore, the concept of life cycle is introduced first. Part 3 starts to record in detail how setState performs synchronous and asynchronous operations on data in development.

Share a daily question: Title portal

Front end e-books: e-book

1. Life cycle

In React, there is no doubt about the role of life cycle. Mastering the life cycle is very helpful to write low redundancy and high-performance components. First of all, let's introduce the life cycle after react16. Although there are few application scenarios for the latest updated content, it is still necessary to understand (borrow the diagram of Cheng Mo Dashen)

1.1 Initialization

In the initialization phase, we usually use the constructor to prepare props and state. Two forms of initialization are recorded here. The first is the introduction of the PageInit class based on the base class component, and the second is the introduction of the Listener class not based on the base class. The difference between the two types is very simple. There is a difference in whether to use the life cycle. Component based classes can use render, CDM, CDU and other life cycles, while Listener classes do not have the conditions to use the life cycle.

PageInit class based on base class:

super () is used to call the constructor () in the base class react.Component, and inject the props of the parent component into the child component (props is read-only). In the child component, props is stored in the internal state for data reading and writing operations (state is variable). Constructor () is used to initialize some functions of the component and pre mount some functions, as shown below:

class PageInit extends react.Component {//Base class based * * class
    constructor(props) {
        super(props);
        this.state = {
            current: 1
        };
        this.IsPage = false;
    }
}

Listener class not based on base class:

The most prominent feature of this class is that it does not need to call the construction method of the base class to avoid in extensions react.component. Once the base class method is called, the state in constructor will no longer be the state injected at the call, but will become props passed from the base class. Method at call:

let mapListenerToStore = new listener(this.mapObj);

As long as the listener is invoked in a component based on a base class, the object is injected into the listener, so the listener and the extension class can share the same object, and the injection and deletion data become synchronous.

class listener {
    constructor(state){//state is the injected this.mapObj
        this.mapObj=state;
        this.newfunction01();
        this.newfunction02();
        this.newfunction03();
        state.map.on('click',this.clickSouth);
        this.clickSouth=null;
    }
}

1.2 mount phase

componentWillMount()

The function is somewhat similar to that of the constructor, which is equivalent to the pre mounted content. This lifecycle is called once the component is mounted to DOM, and calling this.setState method in this cycle will not cause the page to be re rendered. It is rare.

componentWillMount(){
       ..............
}

 

render()

The function is to re render the DOM of the page according to the changes of its own parameters props and state. As a pure function (the return result of a function only depends on its parameters, and there are no side effects in the execution process), render is very suitable for judging the changes of its own parameters. If props and state change, it can re render the page at the first time. Therefore, we cannot call this.setState method in the render cycle, which will result in uninterrupted rendering of the page.

For pure functions, you can refer to this article. The big touch explains it very clearly:

JavaScript - what is a pure functionhttp://12121212

render(){
    return(
     <div>***</div>
)
}
componentDidMount( )

When components are mounted to DOM, the cycle is called, and the whole process is executed only once. It should be noted that after the render of this component is executed, componentDidMount() will not be called, but the CDM cycle will be called only after the render of all sub components is called.

1.3 update phase

The implementation of the update phase is complex, including five basic cycles: componentwillreciveprops > shouldcomponentupdatate > componentwillupdate > render > componentdidupdate. Render is only called in the update phase as a pure function. I think the update stage only needs to understand the change comparison mechanism according to props and state. And the setState method cannot be injected in the hook at this stage. Forced injection will only lead to the non-stop rendering of components. The details are as follows:

componentWillReciveProps(nextProps)

This cycle is only called when the change of props will cause the re rendering of the component. The passed in parameter (nextProps) is the new props passed from the parent component to the child component. However, as mentioned earlier, the render method is only a pure function and does not compare whether the value of state or props changes. As long as the corresponding props or state is passed in, the re rendering of the component will be caused, Therefore, it is necessary to compare this.state in this scope with the nextProps passed in to determine whether to call render again for rendering. Therefore, this life cycle can effectively reduce the number of invalid rendering and improve the development efficiency of the cycle

shouldComponentUpdate(nextProps,nextState)

In this cycle, judge whether to re render by comparing this,props/this.state of the current component and the incoming nextProps/nextState. If there is no change, return false, and the update of the current component stops. If the state and props of the component change, return true and the update of the current component begins. Comparison can also reduce unnecessary rendering of components and optimize component performance

ComponentWillUpdate(nextProps,nextState)

The pre rendering before calling render in this cycle has not been used in development. The principle is to compare the differences between this.props/this and state of the current component and the passed in parameter nextProps/nextState. Render if there are differences.

render

The render cycle is only called

componentDidUpdate(preProps,preState)

The operation with DOM after rendering is performed in this cycle. The parameters passed in are preProps/preState, and the parameters are props and state before component update

It should be noted that the setState method is not allowed to modify the state value during the update phase, otherwise it will cause the render loop to re render

Causes of component update:

1. Parent component render cycle re render

Case 1: in the parent component, render will re render whether the value of props is changed or not, resulting in component update. This problem can be solved by comparing the props of the component itself with the props passed by its parent element through shouldComponentUpdate in the update cycle

class Child extends Component {
   shouldComponentUpdate(nextProps){ 
        if(nextProps.someThings === this.props.someThings){
          return false
        }
    }
    render() {
        return <div>{this.props.someThings}</div>
    }
}

Case 2: converting the received props into its own state in the sub component will cause render to re render.

class Child extends Component {
    constructor(props) {
        super(props);
        this.state = {
            someThings: props.someThings
        };
    }
    render() {
        return <div>{this.state.someThings}</div>
    }
}

We can modify it through the componentwillreceiveprops method. The above code is changed to:

class Child extends Component {
    constructor(props) {
        super(props);
        this.state = {
            someThings:null
        };
    }
    componentWillReceiveProps(nextProps) { // This method is called when the parent component retransmits props
        this.setState({someThings: nextProps.someThings});
    }
    render() {
        return <div>{this.state.someThings}</div>
    }
}

It will not cause redundant rendering.

2. This component calls the setState method

General situation: the setState method is called in the function called in the componentDidMount cycle, and the assignment is different from the state content.

Special case: state does not change or will be re rendered

class Child extends Component {
   constructor(props) {
        super(props);
        this.state = {
          someThings:1
        }
   }

   handleClick = () => { // Although setState is called, the state does not change. It is still someThings:1
        const preSomeThings = this.state.someThings
         this.setState({
            someThings: preSomeThings
         })
   }

    render() {
        return <div onClick = {this.handleClick}>{this.state.someThings}</div>
    }
}

Call shouldComponentUpdate cycle to judge whether the state changes. Add the following code before handleclick:

  shouldComponentUpdate(nextStates){ // This method should be used, otherwise the component will be re rendered whether there is a change in state or not
        if(nextStates.someThings === this.state.someThings){
          return false
        }
    }

Here, the update cycle of the component and the conditions under which the record will be updated are completed.

1.4 unloading cycle

There is only one lifecycle method at this stage: componentWillUnmount

This method is called before the component is unloaded. Some cleaning operations can be performed here, such as clearing the timer used in the component, and clearing the DOM elements created manually in componentDidMount, so as to avoid causing memory leaks.

Common cycle recording completed

Reference article: almin's Explain the React life cycle in detail (including React version 16)

2. New cycle after React16.4

Borrow the periodic chart of Cheng Mo:

 

Two new lifecycle functions are introduced: getDerivedStateFromProps and getSnapshotBeforeUpdate

Because it has not been used in development, record it when it is used (all articles are continuously updated in the iteration of development experience) and attach the summary of great God for learning:

Component lifecycle functions after React v16.3http://121212

The lifecycle record of the component ends

3. setState for asynchronous data processing

Example a: in daily development, we usually use setState method in ComponentDidMount cycle. The characteristics of CDM cycle make setState easy to understand. Details are as follows

class App extends Component {
    state = {
        ante: 0
    }
​
    componentDidMount(){
        this.setState({
           ante: 1
         })
        console.log(this.state.ante) // 0
    }
​
    render(){
        ...
    }
}

Example b: however, in the feature of CDM, it is only executed once after render is executed. In any case, we cannot obtain the updated state in this cycle, but the developer of react architecture provides a callback to obtain the changed state value:

CDM(){
  this.setState({
  ante:1
},()=>{//Implemented through callback
   console.log(this.state.ante)//1
})

}

  Example c: the output result becomes what we want. 1. Principle: the callback function can be obtained immediately after the state is updated. But there is another problem:

state = {
        ante: 0
    }

CDM(){
  this.setState({
  ante:this.state.ante+1
},()=>{//Implemented through callback
   console.log(this.state.ante)//1
})

  this.setState({
  ante:this.state.ante+1
},()=>{//Implemented through callback
   console.log(this.state.ante)//1
})

}

Example d: after calling the setState method several times, the state value printed twice is 1. The reason is that the callback function can only obtain the current modified state, and the state value obtained by executing the setState method again is still the initial state value after mounting. So the solution is  

state = {
        ante: 0
    }

ComponentDidMount(){
  this.setState(preState=>{
  ante:preState.ante+1
},()=>{//Implemented through callback
   console.log(this.state.ante)//1
})

  this.setState(preState=>{
  ante:preState.ante+1
},()=>{//Implemented through callback
   console.log(this.state.ante)//2
})

}

This can realize the continuous accumulation of the initial state in different setState methods. The principle is that the setState itself can accept the function as the parameter, and the parameter we use here is the last state. Check whether you really master the setState method for asynchronous data operation? Try the following example:

state = {
        ante: 0
    }
ComponentDidMount(){ 
    this.setState({ 
      ante: this.state.ante+ 1 });
    console.log("console: " + this.state.ante); // Output 1

    this.setState({ ante: this.state.ante+ 1 }, () => {
      console.log("console from callback: " + this.state.ante); // Output 2
    });

    this.setState(prevState => {
      console.log("console from func: " + prevState.ante); // Output 3
      return {
        ante: prevState.ante+ 1
      };
    }, ()=>{
      console.log('last console: '+ this.state.ante)//Output 4
    });
}

Output results and execution sequence:

Output 1: console:0
 Output 3: console from func: 1
 Output 2: console from callback:2
 Output 4: last console: 2

 

If there is no error, congratulations on knowing the execution queue of state. If there is any doubt, you need to know that the state has an update queue. Each time you call the setState method, you will put the currently modified state into this queue. react queries that the current setState method is executed, merge the state data, execute the callback after the merge, and update the VirtualDom according to the merge results to trigger the render cycle

4. Asynchronous design principle and application scenario of setState

We only need to understand that the role of asynchrony is to improve performance and reduce redundancy. Simply put, because the state has an update queue, accumulating all updates to the end, batch merging and then rendering can greatly improve the performance of the application. Like the source JS, re rendering the DOM after modification will cause huge performance consumption. Similarly, this is also related to the diff algorithm optimized by react. The diff algorithm optimized by react will be recorded in detail later. The official explanation is as follows:

  https://github.com/facebook/react/issues/11527#issuecomment-360199710https://github.com/facebook/react/issues/11527#issuecomment-360199710

 

5. The true face of setState (synchronous application scenario)

In order to solve cross platform problems, react encapsulates a set of event mechanism in JSX and proxies the original source events. For example, onClick, onFocus and other methods added in the dom returned in render in JSX are synthetic events. Therefore, setState is not a real asynchronous operation, but just simulates asynchronous behavior. The implementation determines whether to save into the state queue first or update directly through isBatchingUpdates. If the value is true, the asynchronous operation will be performed, and if false, the direct update will be performed. A typical example is when the setTimeout timer is used, the synchronous operation will be directly used:

state={
count:0
}

componentDidMount() {
    // Call in lifecycle
    this.setState({ count: this.state.count + 1 });
    console.log("lifecycle: " + this.state.count);//0

    setTimeout(() => {
      // Call in setTimeout
      this.setState({ count: this.state.count + 1 });
      console.log("setTimeout: " + this.state.count);//1. Set the delay to 0 and execute immediately
    }, 0);
    document.getElementById("div2").addEventListener("click", this.increment2);
  }
​
  increment = () => {
    // Calling in composite events
    this.setState({ count: this.state.count + 1 });
    console.log("react event: " + this.state.count);
  };
​
  increment2 = () => {
    // Call in native event
    this.setState({ count: this.state.count + 1 });
    console.log("dom event: " + this.state.count);//Click and add one. Keep adding
  };
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>
    );
  }
}

 setState asynchronous, synchronous and advancedhttp://121212

 

In real development, it is not recommended to use the combination of synthetic method and source method.

6. Summary

a: setState   It is "asynchronous" only in synthetic events and hook (), and in native events and hooks ()   setTimeout   Are synchronized.


b:   The "asynchrony" of setState does not mean that it is implemented internally by asynchronous code. In fact, the process and code executed by itself are synchronous, but the calling sequence of synthetic events and hook () is updated before, so it is impossible to get the updated value immediately in the synthetic events and hook functions. The so-called "asynchrony" is formed. Of course, the second parameter setState(partialState) can be used,   callback) to get the updated results.


c:   setState   The batch update optimization of is also based on "asynchronous" (composite event, hook ()). Batch updates will not be performed in the native event and setTimeout. In "asynchronous", if the same value is updated multiple times   setState  ,  setState   The batch update policy of will overwrite it and take the last execution. If it is at the same time   setState   Multiple different values will be consolidated and updated in batch when updating.

reference: Do you really understand setState?

6. How to get the latest state data after setState  

/Setstate There can be two parameters. The first parameter has two methods. The first method is object{}/
constructor(props) {
   super(props);
   this.state = {
     msg:false
   }
 }


setdata(){
   this.setState({
     msg:!this.state.msg
   }
 }
 /The second way is a method/
setdata() {
   this.setState(() => {
     return {
       msg: !this.state.msg
     }
   })
 }
 /The second method can also be obtained in this way/
   this.setState((prevState,props) => {
   console.log(prevState,props) //What you get here is the old value of state false
     return {
        msg: prevState.msg
     }
  /Using the second parameter is a method that outputs the corresponding content/
 setdata() {
   this.setState(() => {
     return {
       msg: !this.state.msg
     }
   }, () => {
     console.log(this.state.msg)//What you get here is the latest value true
   })
 }

Happy Hacking~~~

Recommended reading:  

The front-end interview questions get Baidu Jingdong offer, and the front-end interview questions 2021 and answers

 

Posted by CreativeWebDsign on Wed, 20 Oct 2021 18:28:24 -0700