@levinzhang
2023-02-24T09:11:02.000000Z
字数 2921
阅读 351
本文在React的虚拟DOM、不可变数据结构、生产化构建等方面给出了优化React Web应用的常见技巧。
作者|Piumi Liyana Gunawardhana
译者/策划|张卫滨
在现代Web应用开发中,性能是获得更好的用户体验的关键。使用React能够产生直观的用户界面,为众多应用提供更强大的用户体验,而且无需花费很多的精力来显式地优化性能。但是,开发人员在构建大型应用时,往往受困于性能问题,我们可以采用各种方法来进一步优化React应用的性能。
在本文中,我将讨论提升React前端性能的五个技巧。
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
应该是不可变对象,不应该有太多层级的嵌套对象,并且应该包含原始类型的数据。假设我们要思考一种有效的方式来自动探测复杂属性或状态变更,这里就是不可变数据结构的用武之地了。
使用不可变数据结构背后的理念很简单:与其直接对包含复杂数据的对象进行修改,不如创建一个包含数据变更的副本。因此,识别对象变化就可以像比较两个对象的引用一样容易。
为了自动检查复杂的状态变更,可以组合使用React.PureComponent
和不可变数据结构。例如,借助像Redux这样的状态管理工具,如果应用中的状态是不可变的,我们可以将所有的状态对象保存到一个单独的存储中,这样就可以很容易地实现撤销和重做功能。
如果能够使用提供不可变数据结构的库会更好。在处理高度嵌套的对象时,更新不可变的对象可能是一项挑战。如果你遇到这种问题,可以考虑使用Immer或immutability-helper。这些库能够让我们创建更加易读的代码,同时保留了不可变性的优势。
使用不可变数据结构会带来一些收益:
当创建React应用时,你会看到很多有用的警告和错误信息。在开发软件的过程中,它们能够让发现缺陷和问题变得更加容易。但是,它们在性能方面是有代价的。
因此,如果你要做基准测试或者遇到性能问题,请使用最小化的生产化构建来测试你的React应用。终端用户不需要React在开发环境中运行的这些代码片段。这些不相关的代码都可以在生产环境中移除。
如果你使用create-react-app
进行的项目初始化的话,那么可以使用npm run build
来生成没有这些额外代码的生产化构建。如果你直接使用Webpack的话,那么可以运行webpack -p
(相当于webpack — optimize-minimize — define process.env.NODE ENV=”’production’”
)。
在你项目的/build
子目录下,现在将会包含应用的生产化构建。请记住,这仅在生产化部署时才是必要的,在通常的开发中,依然可以使用npm start
。
在项目初期,你的React应用可能只有几个组件。但是,随着不断添加新的功能和依赖,应用也会随着时间的推移而不断增长。不知不觉中,你可能就会有一个非常庞大的生产化文件。
对于单页的React应用,我们通常会将所有的前端JavaScript代码打包到一个最小化的文件中。对于中小规模的应用来说,这是合理的。但是,当应用的规模扩大时,将这些打包的JavaScript代码发送到浏览器会耗费大量的时间,从而影响应用的性能。
如果你使用Webpack来构建React项目的话,那么可以使用它的代码拆分功能,也就是将要构建的应用代码分割成多个“块(chunk)”。例如,我们可以使用CommonsChunkPlugin
将应用打包成两个独立的文件,也就是将供应商或第三方库的代码与我们的应用代码区分开,并按需传递给浏览器。这样的话,我们最后会得到名为vendor.bundle.js
和app.bundle.js
的两个文件。通过拆分文件,我们的浏览器能够减少缓存并且能够并行加载资源,以尽量减少加载时间的延迟。
按需拆分代码是Webpack的一个优秀特性。它可以将代码分割成较小的块,这些块可以在需要时加载。这实现了最小的初始下载量,减少了应用的加载时间。然后,浏览器可以根据应用的需要下载更多的代码块。
如果你正在展示一个包含大量数据的长列表,那么在给定的时间,你应该仅在可见的视口(viewport)内渲染一部分的数据。数据仅在视口中显示,当列表向下滚动时,更多的数据会被渲染出来。这种技术被称为“窗口化(windowing)”。该技术可以大大减少重新渲染组件所消耗的时间以及所生成的DOM节点的数量,因为每次它仅渲染很小的一部分数据行。
知名的窗口化库是react-window和react-virtualized。它们提供了几个可重用的组件来展示数据表格、列表和表格化的数据。当然,如果你需要满足应用特定的需求,那么可以像Twitter那样不断地开发自己的窗口组件。
React Web应用的性能是由其组件的简洁性决定的。因此,在解决性能问题之前,了解React组件的工作方式、渲染和diffing技术是至关重要的。在React生命周期方法的帮助下,我们可以避免不必要的组件渲染。如果消除了这些限制,应用能够像用户期望的那样流畅运行。所以,在增强React应用的性能时,你应该考虑所有的这些想法。
尽管还有其他各种方法来增强React应用的性能,但我希望这篇文章能帮助你获得优化React前端性能的最佳技巧。