查看原文
其他

优化React前端性能的五个技巧

前端之巅 2023-03-11


作者|Piumi Liyana Gunawardhana
译者 / 策划|张卫滨

在现代 Web 应用开发中,性能是获得更好的用户体验的关键。使用 React 能够产生直观的用户界面,为众多应用提供更强大的用户体验,而且无需花费很多的精力来显式地优化性能。但是,开发人员在构建大型应用时,往往受困于性能问题,我们可以采用各种方法来进一步优化 React 应用的性能。

在本文中,我将讨论提升 React 前端性能的五个技巧。

1. 使用 React.PureComponent 
避免进行协调

React 使用了虚拟 DOM(VDOM)的概念,在这种概念中,像 ReactDOM 这样的库会在内存中维护用户界面的“虚拟”表述,并使其与“实际”的 DOM 保持同步。这个过程叫做协调(reconciliation)。

即便只更新已修改的 DOM 节点,重新渲染也会需要时间。生命周期函数 shouldComponentUpdate() 会在重新渲染过程开始之前被调用,这通常都不是什么问题,但是,如果延迟很明显的话,我们可以通过覆盖 shouldComponentUpdate() 来加速这一过程。该函数的默认实现会返回 true,即让 React 来处理更新,如下所示:

shouldComponentUpdate(nextProps, nextState) {
return true;
}

如果你知道自己的组件在某些情况下不需要更新的话,那么可以从 shouldComponentUpdate() 中返回 false 来完全省略渲染过程。

在 React“纯”组件的帮助下,这种优化策略可以变得更加容易、更加自动化。PureComponent 使用浅层(shallow)属性和状态对比来实现其 shouldComponentUpdate()。

这意味着它会对原始数据类型的值和对象的引用进行对比。因此,在使用 React.PureComponent 时,我们需要满足如下两个先决条件:

  • 组件的 state/props 应该是不可变对象,不应该有太多层级的嵌套对象,并且应该包含原始类型的数据。

  • 所有的子组件应该是“纯”组件或函数式组件。

2. 使用不可变的数据结构

假设我们要思考一种有效的方式来自动探测复杂属性或状态变更,这里就是不可变数据结构的用武之地了。

使用不可变数据结构背后的理念很简单:与其直接对包含复杂数据的对象进行修改,不如创建一个包含数据变更的副本。因此,识别对象变化就可以像比较两个对象的引用一样容易。

为了自动检查复杂的状态变更,可以组合使用 React.PureComponent 和不可变数据结构。例如,借助像 Redux 这样的状态管理工具,如果应用中的状态是不可变的,我们可以将所有的状态对象保存到一个单独的存储中,这样就可以很容易地实现撤销和重做功能。

如果能够使用提供不可变数据结构的库会更好。在处理高度嵌套的对象时,更新不可变的对象可能是一项挑战。如果你遇到这种问题,可以考虑使用 Immer 或 immutability-helper。这些库能够让我们创建更加易读的代码,同时保留了不可变性的优势。

使用不可变数据结构会带来一些收益:

  • 没有副作用(side effect)。

  • 创建、测试和使用不可变的数据对象都很简单。

  • 它们协助我们编写的代码能够快速检测状态变更,而不需要重复检查数据。

  • 它们能够避免时序耦合(temporal coupling)。

3. 使用生产化的构建

当创建 React 应用时,你会看到很多有用的警告和错误信息。在开发软件的过程中,它们能够让发现缺陷和问题变得更加容易。但是,它们在性能方面是有代价的。

因此,如果你要做基准测试或者遇到性能问题,请使用最小化的生产化构建来测试你的 React 应用。终端用户不需要 React 在开发环境中运行的这些代码片段。这些不相关的代码都可以在生产环境中移除。

如果你使用 create-react-app 进行的项目初始化的话,那么可以使用 npm run build 来生成没有这些额外代码的生产化构建。如果你直接使用 Webpack 的话,那么可以运行 webpack -p(相当于 webpack — optimize-minimize — define process.env.NODE ENV=”’production’”)。

在你项目的 /build 子目录下,现在将会包含应用的生产化构建。请记住,这仅在生产化部署时才是必要的,在通常的开发中,依然可以使用 npm start。

4. 多个分块文件

在项目初期,你的 React 应用可能只有几个组件。但是,随着不断添加新的功能和依赖,应用也会随着时间的推移而不断增长。不知不觉中,你可能就会有一个非常庞大的生产化文件。

对于单页的 React 应用,我们通常会将所有的前端 JavaScript 代码打包到一个最小化的文件中。对于中小规模的应用来说,这是合理的。但是,当应用的规模扩大时,将这些打包的 JavaScript 代码发送到浏览器会耗费大量的时间,从而影响应用的性能。

如果你使用 Webpack 来构建 React 项目的话,那么可以使用它的代码拆分功能,也就是将要构建的应用代码分割成多个“块(chunk)”。例如,我们可以使用 CommonsChunkPlugin 将应用打包成两个独立的文件,也就是将供应商或第三方库的代码与我们的应用代码区分开,并按需传递给浏览器。这样的话,我们最后会得到名为 vendor.bundle.js 和 app.bundle.js 的两个文件。通过拆分文件,我们的浏览器能够减少缓存并且能够并行加载资源,以尽量减少加载时间的延迟。

按需拆分代码是 Webpack 的一个优秀特性。它可以将代码分割成较小的块,这些块可以在需要时加载。这实现了最小的初始下载量,减少了应用的加载时间。然后,浏览器可以根据应用的需要下载更多的代码块。

5. 虚拟化长列表

如果你正在展示一个包含大量数据的长列表,那么在给定的时间,你应该仅在可见的视口(viewport)内渲染一部分的数据。数据仅在视口中显示,当列表向下滚动时,更多的数据会被渲染出来。这种技术被称为“窗口化(windowing)”。该技术可以大大减少重新渲染组件所消耗的时间以及所生成的 DOM 节点的数量,因为每次它仅渲染很小的一部分数据行。

知名的窗口化库是 react-window 和 react-virtualized。它们提供了几个可重用的组件来展示数据表格、列表和表格化的数据。当然,如果你需要满足应用特定的需求,那么可以像 Twitter 那样不断地开发自己的窗口组件。

最后的思考

React Web 应用的性能是由其组件的简洁性决定的。因此,在解决性能问题之前,了解 React 组件的工作方式、渲染和 diffing 技术是至关重要的。在 React 生命周期方法的帮助下,我们可以避免不必要的组件渲染。如果消除了这些限制,应用能够像用户期望的那样流畅运行。所以,在增强 React 应用的性能时,你应该考虑所有的这些想法。

尽管还有其他各种方法来增强 React 应用的性能,但我希望这篇文章能帮助你获得优化 React 前端性能的最佳技巧。

今日好文推荐

Spotify移动工程平台迁移:将Android和iOS代码库迁移到Bazel

Google 路线图:Flutter 与 JavaScript、Wasm 集成

Vite 4发布,用更快的SWC替换了Babel

2023年构建前端应用时应考虑的10项基础领域

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存