This article will take you to understand Memoization, introduce why Memoization is needed, and how to implement Memoization in React to improve performance. I hope it will be helpful to everyone!
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 will 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 React official documentationcomponents and props
https://reactjs.org/docs/components-and- props.html
Before discussing the details of Memoization in React, let us first take a look at how React uses virtual DOM to render UI. [Related recommendations: Redis Video Tutorial]
The regular DOM basically contains a set of nodes saved 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.
Updating nodes is faster with the help of efficient DOM tree algorithms, 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, 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 of its child elements, but effectively only updates 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 present 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 named count
. The parent component also calls the child component and passes parameters to it. We also added a 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 the 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;
Whenever we click on the button, the count
value will change. 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 be re-rendered. However, when we run the above code and keep incrementing count
, we get the following output:
Parent render Child render Parent render Child render Parent render Child render
You can experience 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 will re-render 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 is really good for performance that the real DOM is not updated unnecessarily, but we can see that even if there are no actual changes in the child components, the new virtual DOM is created and the difference check 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 diff checking of the virtual DOM, we use Memoization.
在 React 应用的上下文中,Memoization 是一种手段,每当父组件重新渲染时,子组件仅在它所依赖的 props 发生变化时才会重新渲染。如果子组件所依赖的 props 中没有更改,则它不会执行 render 方法,并将返回缓存的结果。由于渲染方法未执行,因此不会有虚拟 DOM 创建和差异检查,从而实现性能的提升。
现在,让我们看看如何在类和函数组件中实现 Memoization,以避免这种不必要的重新渲染。
为了在类组件中实现 Memoization,我们将使用 React.PureComponent。React.PureComponent
实现了 shouldComponentUpdate(),它对 state
和 props
进行了浅比较,并且仅在 props 或 state 发生更改时才重新渲染 React 组件。
将子组件更改为如下所示的代码:
//Child.js class Child extends React.PureComponent { // 这里我们把 React.Component 改成了 React.PureComponent render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
此示例的完整代码显示在这个 sandbox 中。
父组件保持不变。现在,当我们在父组件中增加 count
时,控制台中的输出如下所示:
Parent render Child render Parent render Parent render
对于首次渲染,它同时调用父组件和子组件的 render
方法。
对于每次增加 count
后的重新渲染,仅调用父组件的 render
函数。子组件不会重新渲染。
为了在函数组件中实现 Memoization,我们将使用 React.memo()。React.memo()
是一个高阶组件(HOC),它执行与 PureComponent
类似的工作,来避免不必要的重新渲染。
以下是函数组件的代码:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child); // 这里我们给子组件添加 HOC 实现 Memoization
同时还将父组件转换为了函数组件,如下所示:
//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> ); }
此示例的完整代码可以在这个 sandbox 中看到。
现在,当我们递增父组件中的 count
时,以下内容将输出到控制台:
Parent render Child render Parent render Parent render Parent render
在上面的示例中,我们看到,当我们对子组件使用 React.memo()
HOC 时,子组件没有重新渲染,即使父组件重新渲染了。
但是,需要注意的一个小问题是,如果我们将函数作为参数传递给子组件,即使在使用 React.memo()
之后,子组件也会重新渲染。让我们看一个这样的例子。
我们将更改父组件,如下所示。在这里,我们添加了一个处理函数,并作为参数传递给子组件:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = () => { console.log("handler"); // 这里的 handler 函数将会被传递给子组件 }; console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> ); }
子组件代码将保持原样。我们不会在子组件中使用父组件传递来的函数:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child);
现在,当我们递增父组件中的 count
时,它会重新渲染并同时重新渲染子组件,即使传递的参数中没有更改。
那么,是什么原因导致子组件重新渲染的呢?答案是,每次父组件重新渲染时,都会创建一个新的 handler
函数并将其传递给子组件。现在,由于每次重新渲染时都会重新创建 handle
函数,因此子组件在对 props 进行浅比较时会发现 handler
引用已更改,并重新渲染子组件。
接下来,我们将介绍如何解决此问题。
useCallback()
来避免更多的重复渲染导致子组件重新渲染的主要问题是重新创建了 handler
函数,这更改了传递给子组件的引用。因此,我们需要有一种方法来避免这种重复创建。如果未重新创建 handler
函数,则对 handler
函数的引用不会更改,因此子组件不会重新渲染。
为了避免每次渲染父组件时都重新创建函数,我们将使用一个名为 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有关 Hooks 的更多信息,你可以查看 React 的官方 hooks 文档,或者查看 `React Hooks: How to Get Started & Build Your Own"。
useCallback()
钩子传入两个参数:回调函数和依赖项列表。
以下是 useCallback()
示例:
const handleClick = useCallback(() => { //Do something }, [x,y]);
在这里,useCallback()
被添加到 handleClick()
函数中。第二个参数 [x, y]
可以是空数组、单个依赖项或依赖项列表。每当第二个参数中提到的任何依赖项发生更改时,才会重新创建 handleClick()
函数。
如果 useCallback()
中提到的依赖项没有更改,则返回作为第一个参数提及的回调函数的 Memoization 版本。我们将更改父组件,以便对传递给子组件的处理程序使用 useCallback()
钩子:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = useCallback(() => { // 给 handler 函数使用 useCallback() 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> ); }
子组件代码将保持原样。
此示例的完整代码这个 sandbox 中。
当我们在上述代码的父组件中增加 count
时,我们可以看到以下输出:
Parent render Child render Parent render Parent render Parent render
由于我们对父组件中的 handler
使用了 useCallback()
钩子,因此每次父组件重新渲染时,都不会重新创建 handler
函数,并且会将 handler
的 Memoization 版本传递到子组件。子组件将进行浅比较,并注意到 handler
函数的引用没有更改,因此它不会调用 render
方法。
Memoization 是一种很好的手段,可以避免在组件的 state 或 props 没有改变时对组件进行不必要的重新渲染,从而提高 React 应用的性能。你可能会考虑为所有组件添加 Memoization,但这并不一定是构建高性能 React 组件的方法。只有在组件出现以下情况时,才应使用 Memoization:
在本教程中,我们理解了:
React.memo()
和类组件的 React.PureComponent
实现 MemoizationReact.memo()
之后,子组件也会重新渲染useCallback()
钩子来避免在函数作为 props 传递给子组件时产生重新渲染的问题希望这篇 React Memoization 的介绍对你有帮助!
原文地址:https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/
原文作者:Nida Khan
更多编程相关知识,请访问:编程视频!!
The above is the detailed content of Let's talk about how to use Memoization to improve React performance. For more information, please follow other related articles on the PHP Chinese website!