How to quickly get started with VUE3.0: Get started
In this tutorial, we will learn how to implement Memoization in React. Memoization improves performance by caching the results of function calls and returning those cached results when they are needed again.
We’ll cover the following:
This article assumes you have a basic understanding of class and function components in React.
If you want to check out these topics, you can check out the official React documentation components and props
https://reactjs.org/docs/components-and-props.html
Before discussing the details of Memoization in React, let's first take a look at how React uses virtual DOM to render UI.
A regular DOM basically contains a set of nodes held in the form of a tree. Each node in the DOM represents a UI element. Whenever a state change occurs in the application, the corresponding nodes for that UI element and all its child elements are updated in the DOM tree, which then triggers a UI redraw.
With the help of efficient DOM tree algorithms, updating nodes is faster, but redrawing is slow and may impact performance when the DOM has a large number of UI elements. Therefore, virtual DOM was introduced in React.
This is a virtual representation of the real DOM. Now, whenever there is any change in the state of the application, React does not update the real DOM directly, but creates a new virtual DOM. React will then compare this new virtual DOM with the previously created virtual DOM, find the differences (Translator's Note: that is, find the nodes that need to be updated), and then redraw.
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 child elements, but effectively updates only the necessary and minimal changes in the real DOM.
In the previous section, we saw how React uses a virtual DOM to efficiently perform DOM update operations to improve performance. In this section, we will introduce an example that explains the need to use Memoization in order to further improve performance.
We will create a parent class that contains a button that increments a variable called count
. The parent component also calls the child component and passes parameters to it. We also added console.log()
statement in 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;
The complete code for this example can be viewed on CodeSandbox.
We will create a Child
class that accepts parameters 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;
count
value will change every time we click the button in the parent component. Since the state has changed, the render
method of the parent component is executed.
The parameters passed to the child component do not change every time the parent component is re-rendered, so the child component should not re-render. However, when we run the above code and continue to increment count
, we get the following output:
Parent render Child render Parent render Child render Parent render Child render
You can try out the above example in this sandbox and view the console output.
From the output, we can see that when the parent component re-renders, the child component re-renders even if the parameters passed to the child component remain unchanged. This will cause the child component's virtual DOM to perform a diff check against the previous virtual DOM. Since there are no changes in our child components and all props are unchanged on re-rendering, the real DOM will not be updated.
It's definitely a performance benefit that the real DOM isn't updated unnecessarily, but we can see that a new virtual DOM is created and a diff check is performed even when there are no actual changes in the child components. 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 differential checking of the virtual DOM, we use Memoization.
In the context of React applications, Memoization is a means by which whenever the parent component re-renders, the child component only re-renders when the props it depends on change. If there are no changes in the props that the child component depends on, it will not execute the render method and will return the cached results. Since the render method is not executed, there will be no virtual DOM creation and differential checking, resulting in improved performance.
Now, let's see how to implement Memoization in class and function components to avoid this unnecessary re-rendering.
To implement Memoization in a class component, we will use React.PureComponent. React.PureComponent
implements shouldComponentUpdate(), which does a shallow comparison state
and props
and only re-renders the React component if props or state changes.
Change the child component to code like this:
//Child.js class Child extends React.PureComponent { // Here we change React.Component to React.PureComponent render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
The complete code for this example is shown in this sandbox.
The parent component remains unchanged. Now, when we increment count
in the parent component, the output in the console looks like this:
Parent render Child render Parent render Parent render
For the first rendering, it calls the render
method of both the parent component and the child component.
For each re-render after increasing count
, only the render
function of the parent component is called. Child components will not re-render.
To implement Memoization in Function Components, we will use React.memo(). React.memo()
is a higher-order component (HOC) that performs similar work to PureComponent
to avoid unnecessary re-rendering.
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 to implement Memoization
and also convert the parent component into 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> ); }
The complete code for this example can be seen in this sandbox.
Now, when we increment count
in the parent component, the following will be output to the console:
Parent render Child render Parent render Parent render
Parent renderReact.memo()
In the above example, we see that when we use React.memo()
HOC on a child component, the child component is not re-rendered even though the parent component is re-rendered.
However, one small thing to note is that if we pass a function as a parameter to the child component, the child component will re-render 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 and pass it as a parameter 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> ); }
The child component code will remain as is. We will not use functions passed from parent components in child components:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child);
Now when we increment count
in the parent component it re-renders and re-renders the child component at the same time even though there is no change in the parameters passed.
So, what causes the subcomponent 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, because the handle
function is recreated on every re-render, the child component will find that handler
reference has changed when doing a shallow comparison of the props, and re-render the child component.
Next, we'll cover how to fix this issue.
useCallback()
to avoid more re-rendersThe main problem with child component re-rendering is that the handler
function is recreated, which changes the reference passed to the child component. Therefore, we need a way to avoid this duplication. If the handler
function is not recreated, the reference to the handler
function does not change, so the child component does not re-render.
To avoid having to recreate the function every time the parent component is rendered, 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 hooks documentation, or check out `React Hooks: How to Get Started & Build Your Own".
The useCallback()
hook takes in two parameters: a callback function and a list of dependencies .
The following is an example of useCallback()
:
const handleClick = useCallback(() => { //Do something }, [x,y]);
Here, useCallback()
is added to the handleClick()
function. The second parameter [x, y]
can be an empty array, a single dependency, or a list of dependencies. The handleClick()
function is only recreated whenever any of the dependencies mentioned in the second parameter change.
If the dependencies mentioned in useCallback()
have not changed, then the Memoized version of the callback function mentioned as the first argument is returned. We will change the parent component to use useCallback()
hook on handlers 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> ); }
The child component code will remain as is.
The complete code for this example is in this sandbox.
When we increment count
in the parent component of the above code, we can see the following output:
Parent render Child render Parent render Parent render Parent render
Because we use useCallback()
hook for handler
in the parent component, the handler
function will not be recreated every time the parent component is re-rendered, and the Memoization version of handler
will be passed to the child component. The child component will do a shallow comparison and notice that the reference to the handler
function has not changed, so it will not call the render
method.
Memoization is a good means to avoid unnecessary re-rendering of components when their state or props have not changed, thereby improving the performance of React applications. You might consider adding Memoization to all your components, but that's not necessarily the way to build high-performance React components. Memoization should only be used if the component:
In this tutorial we understood:
React.memo()
for function components and React.PureComponent
for class componentsReact.memo()
useCallback()
avoid re-rendering problems when functions are passed as props to child components.I hope this introduction to React Memoization will be helpful to you!
Original address: https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/Original
author: Nida Khan