“How to avoid potholes?” Another way to think about it is, “Why are there pits?” In code writing, there are usually two kinds of potholes:

  • Inappropriate code was called at the wrong time
  • It was not called when it needed to be called

— From: Bejo article

To avoid the life cycle pit, you need to know what life cycles React has. The hook function lifecycle in different versions of React is similar. The React lifecycle is divided into three phases: mount, update, and destroy. Different phases trigger unused hook functions. Let’s take a look at each one.

React 15 Life cycle

Life cycle test example, test version React 15.7.0

Initial rendering of components (mount)

constructor

Constructor is a class constructor that is executed only once during component initialization to initialize state and bind functions.

constructor(props) {
    console.log("Into the constructor");
    super(props);
    this.state = { text: "This child component text" };
 }
Copy the code

But with the popularity of class attributes, I see a lot of code writing constructor instead of class attributes. The reason for removing Constructor is either:

  • Make your code cleaner
  • Constructor is not part of the React lifecycle
class LifeCycelContainer extends React.Component {
  state = {
    text: "Component text".hideChild: false
  };
  render() {
    return (
      <div className="fatherContainer">
        {this.state.text}
      </div>); }}Copy the code
componentWillMount

This method is also called once at mount time, and is called before the Render method. This method was deprecated in later versions of React. The reason is that with the React asynchronous mechanism, the lifecycle hook may be called multiple times. The most intuitive example is to write asynchronous requests in this method, which can be triggered multiple times.

render

The Render method doesn’t actually manipulate the DOM, it just returns what you need. The reactdom.render method in the mount phase does the real rendering work.

componentDidMount

The componentDidMount method executes, meaning that the initialization of the mount is almost complete. It is used to do something when the component is finished loading, such as making a network request, binding events, or when you are ready to manipulate the DOM, and is called after Render. But does componentDidMount have to be called after the real DOM has been drawn? On the browser side, we can think of it this way.

But in other scenarios, especially React Native, componentDidMount doesn’t mean that the actual interface has been drawn. The view may still be drawing due to machine performance limitations.

Component update phase

componentWillReceiveProps

This method has been deprecated in later versions and replaced by the getDerivedStateFromProps method. This method is useful in the early versions because many people don’t quite understand what triggers this method:

  1. When the parent component changes to child component attributes, this change will drive the subcomponents for property modification, triggering componentWillReceiveProps life cycle.
  2. Triggered when the parent component is a component irrelevant attributes will also trigger a subcomponent componentWillReceiveProps, suggesting that componentWillReceiveProps methods are not necessarily as a result of the parent component to trigger is passed to the child components and introduced the property change.

shouldComponentUpdate

During the update process, the Render method is triggered to generate a new virtual DOM and diff to find the DOM that needs to be modified. This process is time consuming. In practice, we accidentally trigger render. To avoid the performance cost of unnecessary render calls, React lets us decide whether to execute the rest of the declaration cycle in the shouldComponent method, which returns true by default. We can also manually set false and skip the rest of the life cycle.

componentWillUpdate

Executes before the Render function, which does something that doesn’t involve the actual DOM. Later versions have been deprecated.

render

Same as the mount phase

componentDidUpdate

The DOM is updated after the render function is executed. This lifecycle is also often used to handle DOM operations. In addition, we often notify the parent component of the execution of componentDidUpdate as a sign that the child component has been updated.

Component destroyed

componentWillUnmount

The lifecycle that is triggered before a component is unloaded. This function is used to perform cleanup. A common Bug is forgetting to cancel the timer in componentWillUnmount, causing timed operations to continue after the component has been destroyed. Therefore, you must unbind the event and cancel the timer at this stage. Unbinding events and timers can cause unintended problems when writing code.

ComponentWillUnmount fires in two cases

  • Component removed from parent component (destroyed)
  • The component sets the KEY property, and the parent component removes the KEY when re-render finds it inconsistent with the last one

React 16 Life cycle

The life cycle for the Act16X release can be divided into two versions 16.3 and >=16.4. The React life cycle is now available online on the React life cycle page.

Life cycle test example, test version React 16.3.0

Initial rendering of components (mount)

Missing componentWillMount, added getDerivedStateFromProps

Note the getDerivedSatateFromProps not componentWillMount alternatives. Is designed to replace componentWillReceiveProps getRerivedSatateFromProps design. But say to replace componentWillReceiveProps is not entirely correct. The specific reasons will be explained later.

  • GetDerivedStateFromProps is a static method that does not rely on instance storage, so this is not accessible within the getDerivedStateFromProps method.
static getDerivedStateFromProps(props, state) {
    this.xxx // this -> null
}
Copy the code
  • GetDerivedStateFromProps takes two arguments. The first argument is to accept props from the parent component, and the second argument is the state generated by the current component.
  • GetDerivedStateFromProps requires a return value in object format, and React will warn you if you don’t return a value.

  • GetReriverSatateFromProps return values are essential, because the React need to use the return value to update the component of the state. So if you really don’t have a need to “derive state using props”, it’s best to just omit the lifecycle method, otherwise be sure to return null.

  • Note that the update action to state by getDerivedStateFromProps is not an override, but a targeted update.
Other life cycles

Version 16 and version 15 are similar to the rest of the mount lifecycle, which is not explained here.

Component update phase

getRerivedStateFromProps

What is the difference between React16.3 and >=React16.4?

Act16.3 and >= Act16.4 version life cycles are the same in both loading and unloading; the difference is in the update phase. In Act 16.4, updates to components triggered by any factor (including the update process triggered by this.setState and forceUpdate) trigger getRerivedStateFromProps, GetRerivedStateFromProps is triggered only when the parent component is updated in React.16.3.

Keep in mind here that the trigger source for the getRerivedStateFromProps method may vary between versions.

Why use getRerivedStateFromProps componentWillReceivedProps instead?

Actually getRerivedStateProps cannot fully replace componentWillReceivedProps, but guarantee the oneness this method, is a relatively reasonable subtraction. The getRerivedSatetFromProps method is a static method that doesn’t get the this of the component instance, so you can’t do this. Fetch, this. SetState, etc.

Therefore, behind the alternative componentWillReceiveProps getDerivedStateFromProps life cycle, React 16 enforces the best practice of using only getDerivedStateFromProps to map props to state. The purpose is to ensure that the behavior of life cycle functions is more controllable and predictable, and to help developers avoid unreasonable programming methods and life cycle abuse from the root;

getSnapshotBeforeUpdate
// called when the component is updated
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("GetSnapshotBeforeUpdate method executed");
    return "haha";
  }
  // called after component update
  componentDidUpdate(prevProps, prevState, valueFromSnapshot) {
    console.log("ComponentDidUpdate method execution");
    console.log("The value obtained from getSnapshotBeforeUpdate is", valueFromSnapshot);
  }
Copy the code

  • GetSnapshotBeforeUpdate is executed after the Render method and before the DOM update.
  • The return value of getSnapshotBeforeUpdate is taken as the third parameter to componentDidUpdate.
  • GetSnapshotBeforeUpdate gets the DOM before the update and the state and pprops after the update.

Component destroyed

Version 16 destruction phase is the same as version 15. If not, please refer back to the destruction phase of the 15 release declaration cycle.

Essential differences between React15 and React16

React introduced Fiber architecture in version 16. Fiber is a rewrite of the React core algorithm. The core of Fiber is that synchronous rendering becomes asynchronous rendering.

The purpose of the Fiber

Fiber was originally designed to address the problem of the React 15 version where JS are stuck on the main thread for a long time without controlling it. JavaScript runs on the main thread of the browser and happens to run alongside style calculation, layout, and, in many cases, drawing. If the JavaScript runs for too long, it blocks these other tasks and may cause frames to drop.

Fiber core goals

  • Break down interruptible work into smaller tasks
  • Reprioritize, redo, and reuse the work you’re doing
  • Yield back and forth between parent and child tasks to support layout refreshes during React execution
  • Support render() to return multiple elements
  • Better support for Error Boundary

No Fiber architecture

Prior to Act16, a new virtual DOM was generated every time a component was updated. Then diff with the last virtual DOM to find the difference and implement the update. This process is a recursive process. As long as it doesn’t get to the last step, it keeps recursing, and the scary thing is, it’s a serial process, so you can imagine how scary that is.

The recursive call stack for synchronous rendering is very deep, and the rendering process returns layer by layer only if the underlying call returns. But in the process of synchronization, the main thread can’t do anything else until the recursive process is complete, and if the recursive rendering time process, the page will be stuck or jammed.

A Fiber architecture

After React16, the Fiber architecture was introduced. Fiber broke up a large update task into many smaller tasks, and after each small task was completed, the rendering thread would return the main thread to see if there was any higher-priority work that needed to be done to avoid lag. During this process, threads are no longer in a dead-end state, but can be interrupted, which is called “asynchronous rendering”.

Look at the lifecycle workflow from another perspective

One of the major changes since React16 was the introduction of Fiber architecture, which changed synchronous rendering to asynchronous rendering. Asynchronous rendering can be “broken”, but there are rules for “breaking”.

When can I be interrupted?

As shown, in Act16, the life cycle is divided into two phases, Render and Commit. Commit can be subdivided into pre-commmit and COMMIT.

  • Render phase: Purity has no side effects, can be paused, restarted, and terminated.
  • Pre-commit phase: DOM can be read.
  • Commit phase: You can use DOM, run side effects, and schedule updates.

Render phase can be interrupted, commit phase can not be interrupted, synchronous execution.

Why? The Render phase can be interrupted, but not the Commit phase

Since the render phase is invisible to the user, no matter what the user does, the user is not aware of it, but the COMMIT phase involves rendering the real DOM, and it’s too bold to change the view under the user’s eyes.

Why change the life cycle?

We’re looking back at why React was “old and new”. Deprecated in Act16:

  • componentWillMount
  • componentWillUpdate
  • componentReceiveProps

All three lifecycles, the commonality of which is the Render phase, can be repeated. Repeated execution of the process can have many risks:

  • Asynchronous requests for the componentWillxxx method may be triggered multiple times
  • Misuse of setState in the componentWillxxx method results in an infinite loop of repeated rendering.

So, the primary motivation for the React16 lifecycle was asynchronous rendering with Fiber architecture. In the process of transformation, mandatory best practices were implemented for the chronically abused parts of the life cycle.

The potholes of the life cycle

The above describes the different versions of the life cycle, so there are pits in the life cycle. The opening paragraph mentions that the pit appears in:

  • Inappropriate code was called at the wrong time
  • It was not called when it needed to be called

Avoiding these pits is

  • Not calling the wrong code at the right time
  • Make the right call when it needs to be made.

Invalid firing of a function component

A functional component is a stateless component that has no life cycle and can be fired in any case. Look at an example.

import React from "react";
import ReactDom from "react-dom";

function TestComponent(props) {
  console.log("I rerendered it.");
  return <div>Function component: 1</div>;
}

class LifeCycle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "This child component text".message: "This is a child component message."
    };
  }
  changeText = () = > {
    this.setState({
      text: "Modified child component text"
    });
  };
  changeMessage = () = > {
    this.setState({
      text: "Modified child component message"
    });
  };
  render() {
    return (
      <div className="container">
        <button onClick={this.changeText} className="changeText">Modify the child component text content</button>
        <button onClick={this.changeMessage} className="changeText">Modify child component messages</button>
        <p>{this.state.text}</p>
        <p>{this.state.message}</p>
        <p className="textContent">
          <TestComponent />
        </p>
      </div>); }}class LifeCycelContainer extends React.Component {
  state = {
    text: "Parent component text".message: "Parent component message"
  };
  changeText = () = > {
    this.setState({
      text: "Modified parent component text"
    });
  };
  changeMessage = () = > {
    this.setState({
      message: "Modified parent component message"
    });
  };
  render() {
    return (
      <div className="fatherContainer">
        <button onClick={this.changeText} className="changeText">Modify the parent component text</button>
        <button onClick={this.changeText} className="changeText">Modify the parent component message</button>
        <p>{this.state.text}</p>
        <p>{this.state.message}</p>
        <LifeCycle />
      </div>
    );
  }
}

ReactDom.render(<LifeCycelContainer />.document.getElementById("root"));

Copy the code

Function components are rerendered in any case. It doesn’t have a life cycle, but the official way to optimize it is react.Memo.

const MyComponent = React.memo(function MyComponent(props) {
  console.log("Memo: I rerendered it.");
  return <div>Memo function component: 2</div>;
});
Copy the code

Instead of blocking the render, the Memo skips the render component and reuses the last render directly, which is completely different from shouldComponentUpdate.

Invalid trigger of React.Component

Define shouldComponentUpdate to avoid invalid firing

shouldComponentUpdate() {
    // xxxx
    return false;
}
Copy the code

Use react. PureComponent with caution

class LifeComponent extends PureComponent{
    render(){
        return (
            <p>{this.props.title}</p>)}}Copy the code

React.purecomponent is almost identical to react.componentPonent, but the react.pureComponent shouldComponentUpate() with a shallow contrast between prop and state.

If the Render () function of the React component renders the same result given the same props and state, you can use the React.PureComponent to improve performance in certain scenarios.

ShouldComponentUpdate () of the React.pureComponent only makes shallow comparisons to objects. If an object contains a complex data structure, it may generate a false negatives (as when the data at the depth of the object changes views without updating the negatives) due to a deeper data inconsistency. Inherit PureComponent when you expect only simple props and state, or use forceUpate() when you know the underlying data structure has changed. Alternatively, consider using immutable objects to facilitate fast comparisons of nested data.

componentWillMount

ComponentWillMount has been deprecated in React and is not recommended, mainly because the new asynchronous rendering architecture causes it to be called multiple times. So the network request and event binding code should be moved to componentDidMount.

ComponentWillMount performs one or more async rendering before the page initializes render.

Many students request data during this lifecycle to speed up the rendering of the home page, but due to the nature of asynchronous events in JavaScript, when you initiate an API call, the browser will go back and do other work in the meantime. When React renders a component, it doesn’t wait for componentWillMount it to complete anything, React proceeds and continues render, there is no way to “pause” the render to wait for the data to arrive. The componentDidMount operation is a better fit for these operations.

componentWillReceiveProps

Use getDerivedStateFromProps replace new componentWillReceiveProps is marked by the new, on the one hand is the performance problems, on the other hand fundamentally achieve the optimal solution of the code, avoid the side effects.

componentWillUnmount

Remember to handle cleanup operations such as unbinding events and undoing timers in componentWillUnmount to avoid causing unnecessary bugs.

Add boundary processing

By default, a component error during render causes the entire component tree to be uninstalled. Error boundary: a component that catches errors that occur in child components during render and has the ability to prevent further propagation of errors.

The error boundary is a React component that catches and prints JavaScript errors that occur anywhere in its child tree, and instead of rendering the broken child tree, it renders the alternate UI. Error bounds catch errors during rendering, in lifecycle methods, and in constructors throughout the component tree.

Note that error boundaries do not capture errors in the following scenarios: 1. Event handling. Asynchronous code (such as setTimeout or requestAnimationFrame callback). 3. Server-side rendering. 4. Errors thrown by itself (not by its children).

If either (or both) of the static getDerivedStateFromError() or componentDidCatch() lifecycle methods are defined in a class component, it becomes an error boundary. When an error is thrown, render the standby UI using static getDerivedStateFromError() and print the error message with componentDidCatch().

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so that the next rendering can display the degraded UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also report error logs to the server
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can customize the degraded UI and render it
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; }}Copy the code

Source code

Error boundaries work like JavaScript’s Catch {}, except that they only apply to React components. Only class components can be error bound components. In most cases, you only need to declare the error boundary component once and use it throughout the application.

Note that an error boundary can only catch errors in its children, not in itself. If an error boundary cannot render an error message, the error bubbles to the nearest upper error boundary, similar to how catch {} works in JavaScript.

Example of boundary processing

conclusion

On a daily basis, you might run into some of these pitfalls, but these are just a few. It’s possible that some of you said I used React hooks, which have no life cycles at all. Hope to be useful to you, but also hope that more students say you encountered pits, we learn together! skr~~~~