改进和优化 React 应用程序性能的方法

已发表: 2022-03-10
快速总结 ↬自 React 推出以来,它改变了前端开发人员构建 Web 应用程序的方式,其虚拟 DOM 以有效渲染组件而闻名。 在本教程中,我们将讨论在 React 应用程序中优化性能的各种方法,以及我们可以用来提高性能的 React 特性。

React 使 Web 应用程序能够快速更新其用户界面 (UI),但这并不意味着您的中型或大型 React 应用程序将有效地执行。 它的性能将取决于您在构建它时如何使用 React,以及您对 React 的运行方式以及组件在其生命周期的各个阶段所经历的过程的理解。 React 为 Web 应用程序提供了许多性能改进,您可以通过各种技术、功能和工具来实现这些改进。

在本教程中,我们将讨论在 React 应用程序中优化性能的各种方法,以及我们可以用来提高性能的 React 特性。

在 React 应用程序中从哪里开始优化性能?

如果不确切知道优化的时间和地点,我们就无法开始优化应用程序。 你可能会问,“我们从哪里开始?”

在初始渲染过程中,React 构建组件的 DOM 树。 因此,当 DOM 树中的数据发生更改时,我们希望 React 仅重新渲染受更改影响的那些组件,跳过树中未受影响的其他组件。

然而,React 最终可能会重新渲染 DOM 树中的所有组件,即使并非所有组件都受到影响。 这会导致加载时间变长,浪费时间,甚至浪费CPU资源。 我们需要防止这种情况发生。 因此,这是我们将重点关注优化工作的地方。

在这种情况下,我们可以将每个组件配置为仅在必要时渲染或区分,以避免浪费资源和时间。

跳跃后更多! 继续往下看↓

衡量绩效

永远不要根据你的感受开始你的 React 应用程序的优化过程。 相反,使用可用的测量工具来分析您的 React 应用程序的性能,并获得可能会减慢它的详细报告。

使用 Chrome 的性能选项卡分析 React 组件

根据 React 的文档,当您仍处于开发模式时,您可以使用 Chrome 浏览器中的“性能”选项卡来可视化 React 组件如何安装、更新和卸载。 例如,下图显示了 Chrome 的“性能”选项卡在开发模式下分析和分析我的博客。

性能分析器摘要
性能分析器摘要(大预览)

为此,请按照下列步骤操作:

  1. 暂时禁用所有扩展,尤其是 React 开发者工具,因为它们会干扰分析结果。 您可以通过在隐身模式下运行浏览器来轻松禁用扩展程序。
  2. 确保应用程序在开发模式下运行。 也就是说,应用程序应该在您的本地主机上运行。
  3. 打开 Chrome 的开发者工具,点击“性能”标签,然后点击“记录”按钮。
  4. 执行您要分析的操作。 录制时间不要超过 20 秒,否则 Chrome 可能会挂起。
  5. 停止录音。
  6. React 事件将分组在“用户计时”标签下。

分析器中的数字是相对的。 大多数时间和组件将在生产中更快地呈现。 尽管如此,这应该可以帮助您确定何时错误地更新了 UI,以及 UI 更新发生的深度和频率。

React 开发者工具分析器

根据 React 的文档,在react-dom 16.5+ 和react-native 0.57+ 中,使用 React Developer Tools Profiler 在开发人员模式下可以使用增强的分析功能。 Profiler 使用 React 的实验性 Profiler API 来整理每个渲染组件的时间信息,以便识别 React 应用程序中的性能瓶颈。

只需为您的浏览器下载 React Developer Tools,然后您就可以使用它附带的分析器工具。 分析器只能在开发模式或 React v16.5+ 的生产分析构建中使用。 下图是我的博客在开发模式下使用 React Developer Tools Profiler 的分析器摘要:

React Developer Tools Profiler 火焰图
React Developer Tools Profiler 火焰图(大预览)

为此,请按照下列步骤操作:

  1. 下载 React 开发者工具。
  2. 确保您的 React 应用程序处于开发模式或 React v16.5+ 的生产分析版本中。
  3. 打开 Chrome 的“开发者工具”选项卡。 React Developer Tools 提供了一个名为“Profiler”的新选项卡。
  4. 单击“记录”按钮,然后执行您要配置的操作。 理想情况下,在您执行了要分析的操作后停止录制。
  5. 一个图表(称为火焰图)将与您的 React 应用程序的所有事件处理程序和组件一起出现。

注意有关详细信息,请参阅文档。

使用React.memo()进行记忆

React v16 发布了一个额外的 API,一个名为React.memo()的高阶组件。 根据文档,这仅作为性能优化存在。

它的名字“ memo ”来自 memoization,它基本上是一种优化形式,主要用于通过存储昂贵函数调用的结果并在再次调用相同的昂贵函数时返回存储的结果来加速代码。

记忆是一种执行一次函数的技术,通常是一个纯函数,然后将结果保存在内存中。 如果我们尝试使用与之前相同的参数再次执行该函数,它将仅返回第一个函数执行的先前保存的结果,而无需再次执行该函数。

将上面的描述映射到 React 生态系统,提到的函数是 React 组件,参数是 props。

使用React.memo()声明的组件的默认行为是仅在组件中的 props 发生更改时才呈现。 它对道具进行了浅层比较以检查这一点,但可以使用一个选项来覆盖它。

React.memo()通过避免重新渲染 props 未更改或不需要重新渲染的组件来提高 React 应用程序的性能。

下面的代码是React.memo()的基本语法:

 const MemoizedComponent = React.memo((props) => { // Component code goes in here })

何时使用React.memo()

  • 纯功能组件
    你可以使用React.memo()如果你的组件是功能性的,被赋予相同的道具,并且总是呈现相同的输出。 您还可以在带有 React 钩子的非纯功能组件上使用React.memo()
  • 组件经常渲染
    你可以使用React.memo()来包装一个经常渲染的组件。
  • 组件使用相同的道具重新渲染
    使用React.memo()包装在重新渲染期间通常提供相同道具的组件。
  • 中高元素
    将它用于包含中到大量 UI 元素的组件,以检查 props 是否相等。

注意在记忆使用 props 作为回调的组件时要小心。 确保在渲染之间使用相同的回调函数实例。 这是因为父组件可以在每次渲染时提供不同的回调函数实例,这将导致记忆过程中断。 要解决此问题,请确保 memoized 组件始终接收相同的回调实例。

让我们看看我们如何在现实世界中使用记忆。 下面的功能组件,称为“照片”,使用React.memo()来防止重新渲染。

 export function Photo({ title, views }) { return ( <div> <div>Photo title: {title}</div> <div>Location: {location}</div> </div> ); } // memoize the component export const MemoizedPhoto = React.memo(Photo);

上面的代码由一个功能组件组成,该组件显示一个包含照片标题和照片中主题位置的 div。 我们还通过创建一个新函数并将其命名为MemoizedPhoto来记忆组件。 只要在后续渲染中 props、 titlelocation相同,记忆 photo 组件将防止该组件重新渲染。

 // On first render, React calls MemoizedPhoto function. <MemoizedPhoto title="Effiel Tower" location="Paris" /> // On next render, React does not call MemoizedPhoto function, // preventing rendering <MemoizedPhoto title="Effiel Tower" location="Paris" />

在这里,React 只调用了 memoized 函数一次。 只要道具保持不变,它就不会在下一次调用中渲染组件。

捆绑和缩小

在 React 单页应用程序中,我们可以将所有 JavaScript 代码打包并压缩到一个文件中。 这个没问题,只要我们的应用比较小。

随着我们的 React 应用程序的增长,将我们所有的 JavaScript 代码捆绑和压缩到一个文件中变得有问题、难以理解和乏味。 它还会影响我们的 React 应用程序的性能和加载时间,因为我们正在向浏览器发送一个大的 JavaScript 文件。 因此,我们需要一些过程来帮助我们将代码库拆分为各种文件,并根据需要按时间间隔将它们交付给浏览器。

在这种情况下,我们可以使用某种形式的资产捆绑器,例如 Webpack,然后利用其代码拆分功能将我们的应用程序拆分为多个文件。

Webpack 的文档中建议将代码拆分作为改善应用程序加载时间的一种方法。 React 的文档中也建议延迟加载(只提供用户当前需要的东西),这可以显着提高性能。

Webpack 提出了三种通用的代码拆分方法:

  • 入口点
    使用条目配置手动拆分代码。
  • 重复预防
    使用SplitChunksPlugin去重复和拆分块。
  • 动态导入
    通过模块内的内联函数调用拆分代码。

代码拆分的好处

  • 拆分代码有助于浏览器的缓存资源和不经常更改的代码。
  • 它还有助于浏览器并行下载资源,从而减少应用程序的整体加载时间。
  • 它使我们能够将代码拆分成块,这些块将根据需要或应用程序的需要加载。
  • 它使首次渲染时资源的初始下载相对较小,从而减少了应用程序的加载时间。
捆绑和缩小过程
捆绑和缩小过程(大预览)

不可变数据结构

React 的文档谈到了不改变数据的力量。 任何无法更改的数据都是不可变的。 不变性是 React 程序员应该理解的概念。

不可变的值或对象不能更改。 因此,当有更新时,会在内存中创建一个新值,而旧值保持不变。

我们可以使用不可变数据结构和React.PureComponent来自动检查复杂的状态变化。 例如,如果您的应用程序中的状态是不可变的,您实际上可以使用 Redux 之类的状态管理库将所有状态对象保存在单个存储中,从而使您能够轻松实现撤消和重做功能。

不要忘记,一旦创建不可变数据,我们就无法更改它。

不可变数据结构的好处

  • 它们没有副作用。
  • 不可变数据对象易于创建、测试和使用。
  • 它们帮助我们编写可用于快速检查状态更新的逻辑,而无需一遍又一遍地检查数据。
  • 它们有助于防止时间耦合(一种代码依赖于执行顺序的耦合)。

以下库有助于提供一组不可变的数据结构:

  • 不变性助手
    在不更改源的情况下更改数据副本。
  • 不可变的.js
    JavaScript 的不可变持久数据集合提高了效率和简单性。
  • 无缝不可变
    JavaScript 的不可变数据结构与普通的 JavaScript 数组和对象向后兼容。
  • 反应复制写入
    这提供了具有可变 API 的不可变状态。

其他提高性能的方法

在部署之前使用生产构建

React 的文档建议在部署应用程序时使用缩小的生产构建。

React 开发者工具的“生产构建”警告
React 开发者工具的“生产构建”警告(大预览)

避免匿名函数

因为匿名函数没有被分配一个标识符(通过const/let/var ),所以当一个组件不可避免地再次被渲染时,它们就不是持久的。 这会导致 JavaScript 在每次重新渲染此组件时分配新内存,而不是像使用命名函数时那样只分配一次内存。

 import React from 'react'; // Don't do this. class Dont extends Component { render() { return ( <button onClick={() => console.log('Do not do this')}> Don't </button> ); } } // The better way class Do extends Component { handleClick = () => { console.log('This is OK'); } render() { return ( <button onClick={this.handleClick}> Do </button> ); } }

上面的代码显示了两种不同的方法来使按钮在单击时执行操作。 第一个代码块在onClick()属性中使用了一个匿名函数,这会影响性能。 第二个代码块在onClick()函数中使用了一个命名函数,这在这个场景中是正确的方式。

安装和拆卸组件通常很昂贵

不建议使用条件语句或tenaries 使组件消失(即卸载它),因为使组件消失会导致浏览器重绘和重排。 这是一个代价高昂的过程,因为必须重新计算文档中 HTML 元素的位置和几何形状。 相反,我们可以使用 CSS 的opacityvisibility属性来隐藏组件。 这样,组件仍然在 DOM 中,但不可见,没有任何性能成本。

虚拟化长列表

文档建议,如果您要渲染包含大量数据的列表,则应在可见视口内一次渲染列表中的一小部分数据。 然后,您可以在列表滚动时呈现更多数据; 因此,数据仅在视口中时才会显示。 这个过程称为“窗口化”。 在窗口化中,在任何给定时间都会呈现一小部分行。 有一些流行的库可以做到这一点,其中两个由 Brian Vaughn 维护:

  • 反应窗口
  • 反应虚拟化

结论

还有其他几种方法可以提高 React 应用程序的性能。 本文讨论了性能优化的最重要和最有效的方法。

我希望你喜欢阅读本教程。 您可以通过下面列出的资源了解更多信息。 如果您有任何问题,请将其留在下面的评论部分。 我很乐意回答每一个问题。

参考资料和相关资源

  • “优化性能”,React 文档
  • “明智地使用 React.memo”,Dmitri Pavlutin
  • “React 中的性能优化技术”,Niteesh Yadav
  • “React 中的不变性:变异对象没有错”,Esteban Herrera
  • “优化 React 应用程序性能的 10 种方法”,Chidume Nnamdi
  • “提高 React 应用程序性能的 5 个技巧”,William Le