在React中实现备忘录化以提高性能的详细指南

185 阅读5分钟

在本教程中,我们将学习如何在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.PureComponentReact.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 函数。子组件并没有被重新渲染。

继续阅读如何在React中实现Memoization以提高 SitePoint性能