How to upgrade react life cycle beyond 16.4 without hesitation

Why upgrade your code?

This life cycle upgrade is very important because of the asynchronous rendering mechanism that react officially is about to release, i.e. What is React Fiber As a result, it is possible to make multiple calls to the original life cycle that only calls once. It even calls half and then invalidates the call. So the life cycle before the original reader is not safe. Unexpected bug s can occur if you perform side-effects in the lifecycle prior to render, such as asynchronous request interfaces, subscriptions, time-consuming operations, and others. So we need to put these original operations into the life cycle after render is completed, so that we can ensure that the side effects we have only been invoked once. Now let's talk about how some of our previous operations can be upgraded to the new life cycle. So let's start now.

On Compoonent WillMount

Initialization operation

Some people may like to initialize in component WillMount. For example:

class ExampleComponent extends React.Component {
  state = {};

  componentWillMount() {
    this.setState({
      currentColor: this.props.defaultColor,
      palette: 'rgb',
    });
  }
}

The simplest way to do this is to put it in a constructor or in an attribute initializer:

// After
class ExampleComponent extends React.Component {
  state = {
    currentColor: this.props.defaultColor,
    palette: 'rgb',
  };
}

//Or:

class ExampleComponent extends React.Component {
 constructor(props) {
    super(props);
    currentColor: props.defaultColor,
    palette: 'rgb',
  };
}

Getting external data - such as asynchronous calls

There are asynchronous calls in the component WillMount life cycle to obtain data, such as:

// Before
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentWillMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}

In the upcoming asynchronous rendering mode, this lifecycle may be invoked many times, and if you make interface requests here, it may be invoked many times. So the suggestion here is to put the operation of the request interface in the componentDidMount after rendering.

// After
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentDidMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}

Many people think that component WillMount and component WillUnmount are paired. But it's not. Instead, componentDidMount and componentWillUnmount are a pair. They are particularly suited for subscription/cancellation operations. For example: setTimeout, setInterval; request interface / cancel interface in the example above. Here's an example of setInterval:

class Clock extends React.Component {
    constructor(props){
        super(props)
        this.state = getClockTime();
    }
    
    componentDidMount(){
        this.ticking  = setInterval(()=>{
            this.setState(getClockTime();
        },1000);
    }
    
    componentWillUnmount(){
        clearInterval(this.ticking);
    }
    
    render(){
        ...
    }
}

Upgrading of CompoonentWillReceiveProps

The upgrade of this method is the most important. In the past, we used this method to update state. In order to achieve the purpose of controlled components, parent components can control the update of sub-components, obtain data, re-render and other operations. When using the new getDerived StateFromProps method instead of the original componentWillReceiveProps method, some of the original writings are not applicable.

Update state when new props arrive

Following is the use of component WillReceiver Props to update the state based on the new props.

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      this.setState({
        isScrollingDown:
          nextProps.currentRow > this.props.currentRow,
      });
    }
  }
}

Although the above code is not a problem, component WillReceive Props are often used incorrectly. So this method will be abandoned!

Starting with version 16.3, we recommend using stattic getDerived State FromProps (props, state) to update state based on props. The functions of the three abandoned methods will be replaced by this method. That is to say, when a component is mounted, when it updates props, when it updates state, when it updates force update, it calls this method.
In other words. This method is called as long as you render it. Because this method is a static method, it is impossible to get an instance of this component.
Because it is frequently invoked. Similarly, this method is not suitable for time-consuming operations.

Let's take a look at the official examples.

class ExampleComponent extends React.Component {
    state = {
        isScrollingDown:false,
        lastRow:null;
    }
    
    static getDerivedStateFromProps(props,state){
        if(props.currentRow !== state.lastRow){
            return {
                isScrollingDown:props.currentRow > state.lastRow,
                lastRow:props.currentRow,
            }
        }
        
        return null;
    }
}

In the example above: static getDerived StateFromProps (props, state) is a static method because it cannot get an instance of a component. So you can't get the current Row data stored in the current instance object through this.props.currentRow.

In the example above, the same effect is achieved by saving the mirror data in state and comparing it when a new props arrives.

When a new props arrives, asynchronous interface requests are made.

We sometimes have this demand. When new props are updated, we need to retrieve data to update the page, for example:

class ExampleComponent extends React.Component {
    state = {
        externalData:null,
    }
    
    componentDidMount(){
        this._loadAsyncData(this.props.id);
    }
    
    componentWillReceiveProps(nextProps){
        if(nextProps.id !== this.props.id){
            this.setState({externalData:null});
            this._loadAsyncData(nextProps.id)
        }
    }
    
    componentWillUnmount(){
        if(this._asyncRequest){
            this._asyncRequest.cancel();
        }
    }
    
    render(){
        if(this.state.externalData === null){
            //...
        }else {
            //...
        }
    }
    
    _laodAsyncData(id){
        this._asyncRequest = loadMyAsyncData(id).then(
            externalData => {
                this._asyncRequest = null;
                this.setState({externalData});
            }
        );
    }
}

In the example above, we get the data based on the id of props. So we need to monitor id changes in component WillReceive Props. When a change occurs, it triggers asynchronous data acquisition.

The code upgrade here can be said to adhere to the principle that all asynchronous acquisition or side-effect operations are thrown into the back of the life cycle, that is, after render, to ensure that data is executed only once.

This is where data acquisition is put into component DidUpdate. And what about our getDerived State FromProps life cycle?

Update props.id data to state. Empty externalData to null. We get data by judging externalData.

class ExampleComponent extends React.Component {
    state = {
        externalData:null,
    }
    
    static getDerivedStateFromProps(props,state){
         //We save the value of props.id as state.prevID. When the new props.id arrives, we use it for comparison.
        // Clear up the original data so that we can use it to determine whether we need to retrieve the data.
        if(props.id 1== state.prevID){
            return {
                externalData:null,
                prevID:props.id
            }
        }
        return null;
    }
    
    componentDidMount(){
        this._loadAsyncData(this.props.id);
    }
    //After the update is complete, the need to update the data is determined by whether the externalData is empty or not.
    componentDidUpdate(prevProps,prevState){
        if(this.state.externalData === null){
            this._loadAsyncData(this.props.id);
        }
    }
    
    componentWillUnmount(){
        if(this._asyncRequest){
            this._asyncRequest.cancel();
        }
    }
    
    render(){
        if(this.state.externalData === null){
            //...
        }else {
            //...
        }
    }
    
    _laodAsyncData(id){
        this._asyncRequest = loadMyAsyncData(id).then(
            externalData => {
                this._asyncRequest = null;
                this.setState({externalData});
            }
        );
    }
}

Now let's summarize the differences in the use of getDerived StateFromProps and component WillReceive Props:

Corner marker componentWillReceiveProps getDerivedStateFromProps
How to determine whether to update state if(nextProps.id !== this.props.id){} Save the mirror in state. if(props.id 1== state.prevID) is judged by comparing images stored in state.{
How to determine whether data needs to be retrieved The need to retrieve data is known directly by if (nextProps. id!== this. props. id) {} By comparing the images stored in the state, in the example above, we can also set a loading to true by emptying the data. Then the operation of obtaining data is triggered by componentDidUpdate(prevProps,prevState) {} judgment.

It's really a bit cumbersome to compare it with the original method.

About Compoonent WillUpdate

I haven't used this life cycle in actual development. In the official website example, an operation to obtain DOM attributes before render is cited.

Then replace it with a new life cycle getSnapshot BeforeUpdate:

class ScrollingList extends React.Component {
    listRef = null;

    getSnapshotBeforeUpdate(prevProps, prevState) {
        // Are we adding new items to the list?
        // Capture the scroll position so we can adjust scroll later.
        if (prevProps.list.length < this.props.list.length) {
            return (
                this.listRef.scrollHeight - this.listRef.scrollTop
            );
        }
        return null;
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        // If we have a snapshot value, we've just added new items.
        // Adjust scroll so these new items don't push the old ones out of view.
        // (snapshot here is the value returned from getSnapshotBeforeUpdate)
        if (snapshot !== null) {
            this.listRef.scrollTop =
                this.listRef.scrollHeight - snapshot;
        }
    }

    render() {
        return (
            <div ref={this.setListRef}>
                {/* ...contents... */}
            </div>
        );
    }

    setListRef = ref => {
        this.listRef = ref;
    };
}

After getting the data through getSnapshot BeforeUpdate, the data is returned, and then the third parameter of componentDidUpdate(prevProps, prevState, snapshot) gets the data we returned before, and then some operations are performed.

That's it. The new life cycle getDerived StateFromProps I put it in the upgrade section on component WillReceive Props.

over...

Reference Learning Articles:

Update on Async Rendering

Tags: React snapshot Attribute

Posted on Sun, 08 Sep 2019 01:15:15 -0700 by shedokan