Home >Web Front-end >JS Tutorial >How to Implement Memoization in React to Improve Performance
This tutorial will explain how to implement Memoization in React. Memorization improves performance by storing the results of expensive function calls and returning these cached results when needed again.
We will cover the following:
This article assumes that you have a basic understanding of class components and functional components in React. If you want to review these topics, check out the official React components and props documentation.
React.PureComponent
and React.memo()
can be used to implement memory in class components and functional components, respectively. These methods prevent unnecessary re-rendering if the component's props or state has not changed. React.memo()
is used. To avoid this, you can use the useCallback()
hook to prevent recreating the function every time the parent component renders. Before going into the detailed introduction of memory in React, let's first look at how React renders the UI using virtual DOM.
The regular DOM basically contains a set of nodes represented as a tree. Each node in the DOM is a representation of the UI element. Whenever a state change occurs in the application, the corresponding nodes of that UI element and all its child elements are updated in the DOM and then the UI is repainted to reflect the updated changes.
Using efficient tree algorithms, node updates are faster, but re-drawing is slower, and when the DOM has a large number of UI elements, it will have an impact on performance. Therefore, a virtual DOM is introduced in React.
This is a virtual representation of the real DOM. Now, whenever the state of the application changes anyway, React does not directly update the real DOM, but creates a new virtual DOM. React then compares this new virtual DOM with the previously created virtual DOM to find the differences that need to be redrawn.
Using these differences, the virtual DOM will effectively update the real DOM with changes. This improves performance because the virtual DOM does not simply update the UI elements and all its child elements, but effectively updates only the minimum necessary changes in the real DOM.
In the previous section, we saw how React can effectively perform DOM updates using virtual DOM to improve performance. In this section, we will look at a use case that explains why memory is needed to further improve performance.
We will create a parent class with a button to increment the state variable named count. The parent component also calls the child component and passes the prop to it. We also added the console.log() statement to the render method of two classes:
<code class="language-javascript">//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>Count: {this.state.count}</h2> <child name='{"joe"}'></child> </div> ); } } export default Parent;</code>
The full code for this example is available on CodeSandbox.
We will create a Child class that takes the prop passed by the parent component and displays it in the UI:
<code class="language-javascript">//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h2>Name: {this.props.name}</h2> </div> ); } } export default Child;</code>
Whenever we click the button in the parent component, the count value changes. Since this is a state change, the render method of the parent component is called.
Props passed to subclasses remain unchanged every time the parent re-renders, so the child components should not be re-rendered. However, when we run the above code and continue incrementing the count, we get the following output:
<code>Parent render Child render Parent render Child render Parent render Child render</code>
You can increment the count of the above example yourself in the following sandbox and view the output of the console:
[The CodeSandbox link should be embedded here, but since I can't access external websites, it cannot be provided]
From this output we can see that when the parent component re-renders, it also re-renders the child component - even if the props passed to the child component have not changed. This will cause the subcomponent's virtual DOM to perform a difference check with the previous virtual DOM. Since there is no difference in the child component - because props are the same in all re-renders - the real DOM is not updated.
We do have the performance advantage of not updating the real DOM unnecessarily, but we can see here that a new virtual DOM is created and a difference check is performed even if the child components have not actually changed. For small React components, this performance is negligible, but for large components, performance impact is great. To avoid this re-rendering and virtual DOM checking, we use memory.
In the context of a React application, memorization is a technique where whenever the parent component re-renders, the child component will re-render only when props change. If props have not changed, it does not execute the render method, but returns the cached result. Since the render method is not executed, no virtual DOM and differential checks are created - thus improving performance.
Now let's see how memorization is implemented in classes and functional React components to avoid this unnecessary re-rendering.
(The following content is similar to the original text, except that the language and expression have been adjusted a little, and the image location and format are kept unchanged. I cannot provide a CodeSandbox link due to the inability to access external websites.)
To implement memory in class components, we will use React.PureComponent
. React.PureComponent
implements shouldComponentUpdate()
, which makes shallow comparisons between state and props and renders React components only if props or state changes.
Change the child component to the code shown below:
<code class="language-javascript">//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>Count: {this.state.count}</h2> <child name='{"joe"}'></child> </div> ); } } export default Parent;</code>
The full code for this example is as follows: [CodeSandbox link should be embedded here]
The parent component remains unchanged. Now, when we increment count in the parent component, the output in the console is as follows:
<code class="language-javascript">//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h2>Name: {this.props.name}</h2> </div> ); } } export default Child;</code>
For the first rendering, it calls the render method of the parent and child components.
In each incremental subsequent re-render, only the render function of the parent component is called. The child components will not be rerendered.
To implement memory in a functional React component, we will use React.memo()
. React.memo()
is a high-order component (HOC) that does similar work to PureComponent
to avoid unnecessary re-rendering.
The following is the code for functional components:
<code>Parent render Child render Parent render Child render Parent render Child render</code>
We also convert the parent component to a functional component as shown below:
<code class="language-javascript">//Child.js class Child extends React.PureComponent { // 将React.Component更改为React.PureComponent render() { console.log("Child render"); return ( <div> <h2>Name: {this.props.name}</h2> </div> ); } } export default Child;</code>
The full code for this example can be seen in the following sandbox: [CodeSandbox link should be embedded here]
Now, when we increment count in the parent component, the console outputs the following:
<code>Parent render Child render Parent render Parent render</code>
React.memo()
Problems with function prop In the example above, we see that when we use React.memo()
HOC for our child components, even if the parent component is re-rendered, the child components are not re-rendered.
However, a small problem to note is that if we pass the function as a prop to the child component, the child component will be re-rendered even if we use React.memo()
. Let's look at an example.
We will change the parent component as shown below. Here we add a handler function that we pass to the child component as props:
<code class="language-javascript">//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>Name: {props.name}</h2> </div> ); } export default React.memo(Child); // 在此处为子组件添加HOC以进行记忆化</code>
The subcomponent code remains unchanged. We are not using a function passed as props in a child component:
<code class="language-javascript">//Parent.js import React, { useState } from 'react'; import Child from './Child'; 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: {count}</h2> <child name='{"joe"}'></child> </div> ); }</code>
Now, when we increment count in the parent component, it re-renders and re-renders the child component even if props have not changed.
So, what caused the child components to re-render? The answer is that every time the parent component re-renders, a new handler function is created and passed to the child component. Now, since the handler function is recreated every time it is rerendered, the child component will find that the handler reference has been changed when it is a shallow comparison of props and rerenders the child component.
In the next section, we will see how to resolve this issue.
useCallback()
to avoid further rerenderingThe main problem that causes the child component to be re-rendered is the re-creation of the program function, which changes the references passed to the child component. Therefore, we need a way to avoid this recreation. If the handler is not recreated, the reference to the handler will not change - so the child components will not be rerendered.
To avoid recreating the function every time the parent component renders, we will use a React hook called useCallback()
. Hooks were introduced in React 16. To learn more about hooks, you can check out React’s official hook documentation or check out “React Hooks: How to Get Started and Build Your Own.”
useCallback()
Hook accepts two parameters: callback function and dependency list.
Consider the following useCallback()
Example:
<code class="language-javascript">//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>Count: {this.state.count}</h2> <child name='{"joe"}'></child> </div> ); } } export default Parent;</code>
Here, useCallback()
is added to the handleClick()
function. The second parameter [x,y]
can be an empty array, a dependency, or a dependency list. The handleClick()
function is recreated whenever any dependencies mentioned in the second parameter change.
If the dependency mentioned in useCallback()
has not changed, the memorized version of the callback function (as the first parameter) is returned. We will change our parent functional component to use the useCallback()
hook for handlers passed to the child component:
(This is similar to the original text, except that the language and expression are adjusted a little, and the image position and format are kept unchanged. I cannot provide a CodeSandbox link due to the inability to access external websites.)
Memorization is a good technique to improve React application performance by avoiding unnecessary re-rendering of components when the component's props or state has not changed. You might think of adding memorization to just all components, but this is not a good way to build React components. You should use memory only if the component meets the following conditions:
In this tutorial, we have seen:
React.memo()
for functional React components and React.PureComponent
as class componentsReact.memo()
A use case, even after using useCallback()
How to use I hope you find this introduction to React memory useful!
(This is similar to the original text, but some adjustments have been made to the language and expression.)
The above is the detailed content of How to Implement Memoization in React to Improve Performance. For more information, please follow other related articles on the PHP Chinese website!