在本教程中,我们将学习如何在React中实现记忆化。记忆化通过存储昂贵的函数调用的结果,并在再次需要时返回这些缓存的结果来提高性能。
我们将涵盖以下内容。
- React是如何渲染用户界面的
- 为什么需要React的记忆化?
- 我们如何实现功能和类组件的记忆化
- 关于记忆化需要注意的事项
本文假设你对React中的类和功能组件有基本了解。如果你想了解这些主题,请查看React官方关于组件和道具的文档。
React是如何渲染用户界面的
在讨论React中备忘录的细节之前,让我们先看看React是如何使用虚拟DOM渲染UI的。
常规的DOM基本上包含一组节点,以树的形式表示。DOM中的每个节点都是一个UI元素的代表。每当你的应用程序的状态发生变化时,该UI元素的相应节点和它的所有子节点就会在DOM中被更新,然后UI被重新绘制以反映更新的变化。
在高效的树形算法的帮助下,更新节点的速度更快,但重新绘制的速度很慢,当DOM有大量的UI元素时,会对性能产生影响。因此,React中引入了虚拟DOM。
这是真实DOM的一个虚拟表示。现在,只要应用程序的状态有任何变化,React就会创建一个新的虚拟DOM,而不是直接更新真实DOM。然后React将这个新的虚拟DOM与之前创建的虚拟DOM进行比较,找出需要重新绘制的差异。
利用这些差异,虚拟DOM将有效地更新真实DOM的变化。这提高了性能,因为虚拟DOM不是简单地更新UI元素和它的所有子元素,而是只有效地更新真实DOM中的必要和最小的变化。
为什么我们在React中需要记忆化
在上一节中,我们看到React如何使用虚拟DOM有效地执行DOM更新以提高性能。在这一节中,我们将看一个用例,解释对记忆化的需求,以进一步提高性能。
我们将创建一个父类,其中包含一个按钮,用来增加一个名为count 的状态变量。父组件也有一个对子组件的调用,将一个道具传递给它。我们还在两个类的渲染方法中添加了console.log() 语句。
//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;
这个例子的完整代码可以在CodeSandbox上找到。
我们将创建一个Child ,该类接受父组件传递的道具,并在用户界面中显示它。
//Child.js
class Child extends React.Component {
render() {
console.log("Child render");
return (
<div>
<h2>{this.props.name}</h2>
</div>
);
}
}
export default Child;
每当我们点击父组件中的按钮时,计数值就会改变。由于这是一个状态变化,父组件的渲染方法被调用。
传递给子类的道具在每次父类重新渲染时保持不变,所以子组件不应该重新渲染。然而,当我们运行上述代码并不断增加计数时,我们会得到以下输出。
Parent render
Child render
Parent render
Child render
Parent render
Child render
你可以在下面的沙盒中自己为上面的例子增加计数,看看控制台的输出。
从这个输出中,我们可以看到,当父组件重新渲染时,它也会重新渲染子组件--即使传递给子组件的道具没有变化。这将导致子组件的虚拟DOM与之前的虚拟DOM进行差异检查。由于我们在子组件中没有差异--因为所有重新渲染的道具都是一样的--所以真实的DOM并没有更新。
我们确实有一个性能上的好处,即真实的DOM不会被不必要地更新,但我们可以看到,即使在子组件中没有实际的变化,新的虚拟DOM也被创建并进行了差异检查。对于小的React组件,这种性能可以忽略不计,但对于大的组件,性能影响很大。为了避免这种重新渲染和虚拟DOM检查,我们使用了备忘化。
React中的记忆化
在React应用的背景下,备忘化是一种技术,每当父组件重新渲染时,子组件只有在道具发生变化时才会重新渲染。如果道具没有变化,它就不会执行渲染方法,而会返回缓存的结果。由于渲染方法没有被执行,就不会有虚拟的DOM创建和差异检查--从而给我们带来性能上的提升。
现在,让我们看看如何在类和功能React组件中实现记忆化,以避免这种不必要的重新渲染。
在类组件中实现记忆化
为了在类组件中实现记忆化,我们将使用React.PureComponent。React.PureComponent 实现shouldComponentUpdate(),它对状态和道具进行浅层比较,只有在道具或状态发生变化时才渲染React组件。
将子组件改为如下所示的代码。
//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;
这个例子的完整代码显示在下面的沙盒中。
父组件保持不变。现在,当我们增加父组件中的计数时,控制台中的输出如下。
Parent render
Child render
Parent render
Parent render
对于第一次渲染,它同时调用父组件和子组件的渲染方法。
在随后的每次增量的重新渲染中,只调用父组件的render 函数。子组件并没有被重新渲染。