• How to Implement Memoization in React to Improve Performance
  • By Nida Khan
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Zavier
  • Proofreader: jjCatherine, FinalWhy

In this tutorial, we’ll learn how to implement Memoization in React. Memoization improves performance by caching the results of function calls and returning those cached results when needed again.

We will introduce the following:

  • How does React render views?
  • Why is Memoization necessary?
  • How do I implement Memoization in function components and class components?
  • Matters needing attention

This article assumes you have a basic understanding of the classes and function components in React. If you want to check out these topics, check out the React components and Props documentation.

How does React render views?

Before we get into the details of Memoization in React, let’s take a look at how React renders the UI using the virtual DOM.

The regular DOM basically consists of a set of nodes stored as a tree. Each node in the DOM represents a UI element. Each time there is a state change in the application, the corresponding nodes of the UI element and all its children are updated in the DOM tree, which triggers a UI redraw.

Nodes can be updated faster with the help of efficient DOM tree algorithms, but redrawing is slow, and performance can be affected when the DOM has a large number of UI elements. Hence the introduction of the virtual DOM in React.

This is a virtual representation of the real DOM. React now creates a new virtual DOM instead of directly updating the real DOM whenever there is any change in the application’s state. React then compares the new virtual DOM to the one created earlier, finds any differences, and redraws it.

Based on these differences, the virtual DOM can update the real DOM more efficiently. This improves performance because the virtual DOM does not simply update the UI element and all its children, but effectively updates only the necessary and minimal changes in the actual DOM.

Why is Memoization necessary?

In the previous section, we saw how React uses the virtual DOM to efficiently perform DOM updates to improve performance. In this section, we introduce an example that explains the need for Memoization in order to further improve performance.

We will create a parent class with a button to increment a variable named count. The parent component also calls the child component and passes parameters to it. We also added a console.log() statement to the Render method:

//Parent.js
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () = > {
    this.setState((prevState) = > {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe} "/ >
      </div>); }}export default Parent;
Copy the code

The full code for this example is available at CodeSandbox.

We will create a Child class that takes the arguments passed by the parent component and displays them in the UI:

//Child.js
class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>); }}export default Child;
Copy the code

The count value changes every time we click the button in the parent component. Since the state changes, the render method of the parent component is executed.

The parameters passed to the child component do not change each time the parent component is re-rendered, so the child component should not be re-rendered. However, when we run the code above and increment count, we get the following output:

Parent render
Child render
Parent render
Child render
Parent render
Child render
Copy the code

You can experiment with the above example in the sandbox and see the console output.

From the output, we can see that when the parent component is re-rendered, the child component is re-rendered even if the parameters passed to the child component remain unchanged. This causes the child component’s virtual DOM to perform a difference check compared to the previous virtual DOM. Since there are no changes in our child components and none of the props for the re-rendering are changed, the actual DOM will not be updated.

There is a performance benefit to the real DOM not being updated unnecessarily, but we can see that even if there are no actual changes in the child component, a new virtual DOM is created and difference checking is performed. For small React components, this performance cost is negligible, but for large components, the performance impact can be significant. To avoid this re-rendering and difference checking of the virtual DOM, we used Memoization.

The React of Memoization

In the context of the React application, Memoization is a means that whenever the parent component is re-rendered, the child component is re-rendered only when the props it depends on change. If there are no changes in the props on which the child component depends, it will not execute the Render method and will return the cached result. Since the render method is not executed, there is no virtual DOM creation and difference checking, resulting in performance improvements.

Now let’s look at how to implement Memoization in class and function components to avoid this unnecessary rerendering.

The class component implements Memoization

To implement Memoization in the class component, we will use the React.pureComponent. The React.PureComponent implements shouldComponentUpdate(), which makes a shallow comparison between state and props and only re-renders the React component when the props or state changes.

Change the child component to the code shown below:

//Child.js
class Child extends React.PureComponent { // Here we change react.componentto react.pureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>); }}export default Child;
Copy the code

The complete code for this example is shown in the sandbox.

The parent component remains unchanged. Now, when we add count to the parent component, the output in the console looks like this:

Parent render
Child render
Parent render
Parent render
Copy the code

For the first rendering, it calls the Render methods of both the parent and child components.

For rerendering after each increment of count, only the render function of the parent component is called. Child components are not re-rendered.

The function component implements Memoization

To implement Memoization in the function component, we will use react.Memo (). React.memo() is a high-level component (HOC) that performs a similar task to the PureComponent to avoid unnecessary rerendering.

Here is the code for the function component:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); // Here we add HOC to the child component for Memoization
Copy the code

It also converts the parent component to a function component, as shown below:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () = > {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe} "/ >
    </div>
  );
}
Copy the code

The complete code for this example can be seen in the sandbox.

Now, when we increment count in the parent component, the following is printed to the console:

Parent render
Child render
Parent render
Parent render
Parent render
Copy the code

Problems with react.Memo ()

In the example above, we saw that when we used react.Memo () HOC on the child, the child was not re-rendered even though the parent was.

One minor issue to note, however, is that if we pass a function as an argument to a child component, it will be re-rendered even after using react.Memo (). Let’s look at an example of this.

We will change the parent component as shown below. Here we add a handler function that is passed as an argument to the child component:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () = > {
    setCount(count + 1);
  };

  const handler = () = > {
    console.log("handler");    // The handler function here will be passed to the child component
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}
Copy the code

The child component code will remain intact. We do not use functions passed from the parent in the child component:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);
Copy the code

Now, when we increment the count in the parent component, it will re-render and re-render the child component at the same time, even if there are no changes in the passed parameters.

So, what causes the child component to be rerendered? The answer is that every time the parent component rerenders, a new handler function is created and passed to the child component. Now, because the handle function is recreated each time it is re-rendered, the child component finds the handler reference changed when it makes a shallow comparison to the props and rerenders the child component.

Next, we’ll show you how to solve this problem.

throughuseCallback()To avoid more repetitive rendering

The main problem that caused the child component to be re-rendered was that the handler function was recreated, which changed the reference passed to the child component. Therefore, we need a way to avoid this duplication of creation. If the handler function is not recreated, the reference to the handler function does not change, so the child component is not re-rendered.

To avoid recreating the function every time the parent component is rendered, we’ll use a React Hook called useCallback(). Hooks were introduced in React 16. To learn more about Hooks, check out the Official Hooks documentation for React, or check out ‘React Hooks: How to Get Started & Build Your Own’.

The useCallback() hook takes two arguments: a callback function and a list of dependencies.

Here is an example of useCallback() :

const handleClick = useCallback(() = > {
  //Do something
}, [x,y]);
Copy the code

Here useCallback() is added to the handleClick() function. The second argument [x, y] can be an empty array, a single dependency, or a list of dependencies. The handleClick() function is recreated whenever any of the dependencies mentioned in the second argument change.

If the dependency mentioned in useCallback() has not changed, the Memoization version of the callback function mentioned as the first argument is returned. We’ll change the parent component to use the useCallback() hook on the handler passed to the child component:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () = > {
    setCount(count + 1);
  };

  const handler = useCallback(() = > { // Use useCallback() for the handler function
    console.log("handler"); } []);console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}
Copy the code

The child component code will remain intact.

The complete code for this example is in this sandbox.

When we add count to the parent component of the code above, we see the following output:

Parent render
Child render
Parent render
Parent render
Parent render
Copy the code

Because we used the useCallback() hook on the handler in the parent component, the handler function is not recreated every time the parent component rerenders, and the Memoization version of the handler is passed to the child component. The child makes a shallow comparison and notices that the reference to the handler function hasn’t changed, so it doesn’t call the Render method.

Something worth noting

Memoization is a great way to improve the performance of React applications by avoiding unnecessary re-rendering of components when their state or props have not changed. You might consider adding Memoization to all components, but that’s not necessarily the way to build high-performance React components. Memoization should only be used if the component:

  • Fixed input with fixed output
  • With many UI elements, virtual DOM checking affects performance
  • Pass the same argument multiple times

conclusion

In this tutorial, we have learned:

  • How does React render the UI
  • Why is Memoization necessary
  • How to use function components in ReactReact.memo()And class componentsReact.PureComponentImplement Memoization
  • Show through an example even in useReact.memo()After that, the child components are also re-rendered
  • How to useuseCallback()Hooks to avoid rerendering problems when functions are passed to child components as props

Hope this React Memoization introduction was helpful!

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.