React was officially released a month ago with v16.3. In addition to the much-discussed new Context API, two new lifecycle functions, getDerivedStateFromProps, GetSnapshotBeforeUpdate and v17.0 version will be removed in the future of the three lifecycle function componentWillMount, componentWillReceiveProps, ComponentWillUpdate is also worth taking a moment to explore the reasons behind it and how it can be upgraded in a specific project.

componentWillMount

There is no data on the first screen, causing a blank screen

In React applications, many developers put the data request part of the code in componentWillMount in order to avoid the first rendering of the page because it did not receive asynchronous data. They hope to avoid the white screen and send asynchronous requests earlier. In fact, the first rendering of componentWillMount is already underway, so if asynchronous data has not been retrieved by the time componentWillMount is executed, the page will still be rendered without asynchronous data. In other words, the component will always be in a state with no asynchronous data when first rendered, so no matter where the data request is sent, this problem cannot be solved directly. Regarding sending data requests early, it is also officially encouraged to place the data request part of the code in the component’s constructor instead of componentWillMount.

Another common use case for componentWillMount is to fetch data at server render time, since componentDidMount is not called at server render time. In view of this problem, the author provides two solutions. The first simple solution is to put all data requests in componentDidMount, which only requests asynchronous data on the client side. This avoids the need to request the same data twice on the server and the client (componentWillMount is also called when the client renders), but the obvious drawback is that the server does not get all the data required for page rendering. Therefore, if we need to ensure that the HTML returned by the server is the HTML that the user finally sees, we can separate out the data acquisition logic of each page and then correspond to the corresponding page one by one, and find the corresponding data request on the server according to the route of the current page. Use chained promises to stuff data into the Redux Store or other data management tool before rendering the final page, so that the HTML returned by the server contains asynchronous data.

Event subscription

Another common use case is to subscribe to events in componentWillMount and unsubscribe from the corresponding event subscription in componentWillUnmount. React does not guarantee that when componentWillMount is called, the same componentWillUnmount will also be called. In a current version of the example, componentWillUnmount is not called on the server when rendered on the server, so subscriing to events in componentWillMount directly causes a memory leak on the server. On the other hand, in the future, React will enable asynchronous rendering mode, after componentWillMount is called, component rendering may be interrupted by other transactions, resulting in componentWillUnmount will not be called. ComponentDidMount does not have this problem. After componentDidMount is called, componentWillUnmount must be called later, and the component will be cleaned up according to the specific code.

Upgrade package

Migrate the code from existing componentWillMount to componentDidMount.

componentWillReceiveProps

Update the state determined by props and handle callbacks in specific cases

In older versions of React, there was never an elegant way to update state if a component’s own state was related to its props. It requires both before and after judgment in componentWillReceiveProps props are the same, if different then the new props to update to the corresponding state. Doing so breaks the single source of state data, making the state of the component unpredictable and, on the other hand, increasing the number of component redraws. There are many similar service requirements, such as a list that can be swiped horizontally. The current highlighted Tab obviously belongs to the state of the list itself, but in many cases, the service requirements require that when jumping to the list from the outside, a value is passed in to directly locate a Tab.

React provides a more concise lifecycle function:

static getDerivedStateFromProps(nextProps, prevState)
Copy the code

A simple example is as follows:

// before
componentWillReceiveProps(nextProps) {	
  if(nextProps.translateX ! = =this.props.translateX) {
    this.setState({	
      translateX: nextProps.translateX, }); }}// after
static getDerivedStateFromProps(nextProps, prevState) {
  if(nextProps.translateX ! == prevState.translateX) {return {
      translateX: nextProps.translateX,
    };
  }
  return null;
}
Copy the code

At first glance, there may not seem to be a fundamental difference between the two, but this is a change that I think shows the React team’s deep understanding of software engineering. The React team is trying to use framework-level apis to constrain or help developers write more maintainable JavaScript code. To illustrate this, let’s look at another piece of code:

// before
componentWillReceiveProps(nextProps) {
  if(nextProps.isLogin ! = =this.props.isLogin) {
    this.setState({	
      isLogin: nextProps.isLogin,	
    });
  }
  if (nextProps.isLogin) {
    this.handleClose(); }}Copy the code
// after
static getDerivedStateFromProps(nextProps, prevState) {
  if(nextProps.isLogin ! == prevState.isLogin) {return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {
  if(! prevState.isLogin &&this.props.isLogin) {
    this.handleClose(); }}Copy the code

Generally speaking, in componentWillReceiveProps, we usually do the following two things: one is based on the props to update the state, the second is to trigger some correction, such as animation or page jump. In the old version of the React, these two things we all need to do in the componentWillReceiveProps. In the new version, the update state and trigger callback are reassigned to getDerivedStateFromProps and componentDidUpdate, making the overall update logic of the component clearer. Also, getDerivedStateFromProps disallows the component from accessing this. Props, forcing the developer to compare the values of nextProps and prevState. To make sure that when developers use the getDerivedStateFromProps lifecycle function, they are updating the component’s state based on the current props, rather than doing something else that makes the component’s state more unpredictable.

Upgrade package

Existing componentWillReceiveProps code according to the update of the state or callback, respectively in getDerivedStateFromProps and componentDidUpdate accordingly rewritten. Note the differences between the old and new life cycle functions prevProps, this.props, nextProps, prevState, this.state.

componentWillUpdate

Handle side effects due to props changes

Like componentWillReceiveProps, many developers will be according to the variation of props in componentWillUpdate to trigger some callback. But whether componentWillReceiveProps componentWillUpdate, is likely to be in an update to be called many times, that is to say, write here the callback function may also be called many times, this is obviously not desirable. Just like componentDidMount, componentDidUpdate is called only once in an update. Migrating the callback originally written in componentWillUpdate to componentDidUpdate solves this problem.

Read DOM element state before component update

Another common use case for componentWillUpdate is to read the current state of a DOM element in componentDidUpdate before the component is updated. However, with React enabled in asynchronous rendering mode, the RENDER and COMMIT phases are not seamless, which means that the DOM element states read in render are not always the same as those read in commit. This makes it unsafe to use the DOM element states read in componentWillUpdate in componentDidUpdate because the values are likely to be invalid.

React provides a new lifecycle function to solve the problem mentioned above:

getSnapshotBeforeUpdate(prevProps, prevState)
Copy the code

Unlike componentWillUpdate, getSnapshotBeforeUpdate is called before final render, That is, the DOM element state read in getSnapshotBeforeUpdate is guaranteed to be the same as that read in componentDidUpdate. Although getSnapshotBeforeUpdate is not a static method, we should try to use it to return a value. This value is then passed to componentDidUpdate, which then updates the component state in componentDidUpdate rather than getSnapshotBeforeUpdate.

Here’s an official example:

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;
  };
}
Copy the code

Upgrade package

Migrate the callback function from the existing componentWillUpdate to componentDidUpdate. If the state of a DOM element is required to trigger some callback functions, migrate the comparison or evaluation to getSnapshotBeforeUpdate, and then trigger the callback or update state uniformly in componentDidUpdate.

summary

Finally, let’s take a look at the React lifecycle function as a whole:

Before

After

The three life cycle functions circled in red in the first figure are the ones that will be removed in the new version. From the above two figures, we can clearly see that the three lifecycle functions to be removed are all called before render. According to the original design, all three lifecycle functions can do things like send requests, setState, etc., which have side effects. In older versions of React, this might have resulted in a performance penalty, but with React enabled in asynchronous rendering mode, this is no longer an acceptable side effect. An example of Git is when a developer commits 10 file updates and then makes additional updates to current or other files, but still pushes only the 10 file updates that were just committed. This can cause the commit record to be different from the actual update. To avoid this problem, you need to ensure that every file update goes through the COMMIT phase and is committed to the remote end. This is exactly what React does after enabling asynchronous rendering mode.

On the other hand, in order to verify my understanding and test the stability of the new version, I have upgraded several projects in my charge to React 16.3 and replaced all the life cycle functions that will be removed according to the upgrade scheme mentioned above. So far, all projects have performed well in the production environment without any adverse user feedback.

Of course, these lifecycle changes won’t be implemented until React 17.0, giving React developers plenty of time to get used to the changes. But if you’re a maintainer of the React open source project, especially the component library, take the time to take a closer look at this life-cycle function change. Not only will it help you upgrade your open source projects to the latest version of React, but it will also help you understand the upcoming asynchronous rendering mode ahead of time.

I also believe React will likely see an overall performance boost for many commonly used components once asynchronous rendering mode is officially enabled. Furthermore, with asynchronous rendering, many of today’s complex components can be handled more elegantly, with more fine-grained control at the code level, and ultimately a more intuitive user experience.


Personal Blog address: AlanWei/ Blog