[译] 用 React Hooks 和调试工具提升应用性能

栏目: 服务器 · 发布时间: 5年前

内容简介:在构建 React 应用时,你会发现随着嵌套组件增多,用户界面的某些部分开始变得缓慢迟滞。这是因为,被改变 state 的元素在组件树中的层级越高,浏览器需要重绘的组件越多。本文将告诉你如何在CLEVER°FRANKE 的一个客户端项目中,我做过一个过滤器组件,该组件包含一个展示步数的直方图。
[译] 用 React Hooks 和调试 <a href='https://www.codercto.com/tool.html'>工具</a> 提升应用性能

在构建 React 应用时,你会发现随着嵌套组件增多,用户界面的某些部分开始变得缓慢迟滞。这是因为,被改变 state 的元素在组件树中的层级越高,浏览器需要重绘的组件越多。

本文将告诉你如何 通过备忘(memoization)技术避免不必要的重绘,让你的 React 应用快如闪电。:zap:

在CLEVER°FRANKE 的一个客户端项目中,我做过一个过滤器组件,该组件包含一个展示步数的直方图。

[译] 用 React Hooks 和调试工具提升应用性能

我发现每当拖拽过滤器的操纵杆,动画帧率就会骤降,导致组件失去效用。故此我决定一探究竟。

抽丝剥茧

只有明白用户拖拽操纵杆时的内部运作原理,才能确定从何处下手调试。React 使用虚拟 DOM 来代表 DOM 中真实的元素。每当用户操作界面元素,应用的 state 都会改变。React 会遍历所有受 state 改变影响的组件,计算生成新的虚拟 DOM。React 将新旧版本的虚拟 DOM 进行比较,若发现二者有差异,就将对应的变化更新到真实 DOM 上。该过程叫做reconciliation。

操纵 DOM 元素可是一个非常耗费资源的任务。同样,遍历所有受影响组件的 render 函数也很耗时, render 函数中的计算量很大时尤其如此。因此我们要尽量减少这些 浪费性渲染

现在回到我们的案例:因为过滤器组件的 state 由其父组件掌控,所以我的推论是可能发生了不必要的渲染和计算。为了快速确诊,我们要使用 Chrome 调试工具。它有个 Paint Flashing 功能,可以将发生改变的 DOM 高亮显示。你可以在 Rendering 标签页临时激活该功能:

[译] 用 React Hooks 和调试工具提升应用性能

一经激活,浏览器就会显示哪些元素被重绘了。在本案例中效果如下:

[译] 用 React Hooks 和调试工具提升应用性能

看起来合情合理,只有我操纵的组件引发了 DOM 操纵。也就是说浏览器没有做不必要的绘制。那我们就要进一步深入来探究原因了。

为了把 React 组件重绘的情况看得更真切,我们得用 React 调试工具 中一个类似的工具。它叫做 Highlight Updates ,你可以在 React 调试工具的首选项面板中找到它。激活后,它会高亮显示所有正在渲染的组件。如果渲染时间过长,它还会用特殊颜色标识出来。

[译] 用 React Hooks 和调试工具提升应用性能

React 调试工具使你能够检查 React 组件层级,以及对应组件的 props 和 state。

它有浏览器插件(支持Chrome 和Firefox)和 独立应用 (支持 Safari、IE、和 React Native 等运行环境)两种形式。

这里就清晰地揭示了问题所在:当我在一个过滤器上拖拽,包含直方图的另一个过滤器也被重绘了。这就是应该被避免的处理器资源浪费。像直方图这样的笨重组件尤其如此。

现在我们知道了问题所在,但还不知道导致界面响应缓慢的原因。为了找到原因,我们可以使用 Chrome 调试工具的 Performance 面板。它可以帮助你查看在浏览器在执行某一特定任务的过程中,每一帧具体做了什么。

关于 Performance 面板的使用细节,不在本文讨论范围之内。但你可以在这里找到教程。

我使用 Performance 面板记录了过滤器组件中的一次变更。放大火焰图后,我有了以下发现:

[译] 用 React Hooks 和调试工具提升应用性能

如你所见,这两个火焰图大体相同。第一张图(在 Timings 下方)展示了 React 组件的实际的加载和更新。React 调用了用户时间接口,所以我们能看到这张额外的图。第二张图展示了主线程上执行的所有任务,这张图更为详细。

我更喜欢用第一张图来看哪些组件性能差,用第二张图深入了解具体哪个函数和计算过程耗时更多。

第一次看到 Performance 面板的默认火焰图,你可能会被吓到。万幸 React 调试工具也有一个相似功能,在 Profiler 标签页中,能够根据用户时间接口生成同样的火焰图。我认为 React 调试工具中的火焰图更易于理解,而且它还有很多趁手的附加功能:

  • 你可以根据组件渲染时长将所有组件排序(见下方截图)。
  • 你可以快速浏览不同的渲染记录。
  • 你可以点击某组件查看特定渲染阶段的 props
[译] 用 React Hooks 和调试工具提升应用性能

以上图形揭露了罪魁祸首: Histogram 。特别是渲染第二个直方图(右侧那个),耗费了很长时间(402.8 毫秒!),即使我根本没有拖拽它。我们破案了!接下来就该修复问题、优化组件性能了。

注意:我记录性能时打开了 CPU 节流功能,用 1/4 倍速模拟那些并非使用最新版 Macbook Pro 的用户,以此来突显性能问题。

提升组件性能

为防止浪费性渲染的发生,我们可以通过备忘技术优化组件。我们要使用 React.memo 来记忆组件,用 React 的备忘 hooks useMemouseCallback 记忆变量和函数。

React.memo

16.6.0 版本起,React 就支持高阶组件 React.memo 了。它等价于 React.PureComponent ,但只适用于函数组件。社区正逐步从 class 的组件风格转向带有 hooks 的函数组件风格,而 React.memo 正是这种组件。

当你用 React.memo 包裹一个函数组件时,它会将传入的 props 进行浅层比较。当比较的 props 不一致时,才会重新渲染组件。你也可以自己写一个比较函数,作为第二个参数传入。但要慎用,以避免意外故障。

我们可以将组件分解为更小的组件,并把每个更小的组件都用 React.memo 包裹起来。如此你能保证当 props 更新,仅有组件的一部分重新渲染了。但也不要把所有东西都做备忘,因为比较 props 所花时间可能要比渲染组件的时间还要长。

在本文案例中,我用 React.memo 包裹了过滤器组件( RangeSlider )和 Histogram 组件。此外,我把直方图分解为包裹组件和 HistogramBuckets 组件两部分,将逻辑部分和展现部分剥离开来。

const RangeSlider = React.memo(props => {
   ...
});
复制代码

备忘 hooks

React 16.8.0 版本为我们带来功能强大的 hooks,有了 hooks,我们可以轻松备忘组件中的值和回调函数。在引入 hooks 之前,你当然也可以用一个单独的库实现备忘功能,但自从它成为 React 原生库的一部分,集成和塑造工作流变得更加简单易行。

useMemo 会记忆一个值,这样就不用在下一轮渲染中重新计算它了。 useCallback 记忆的则是回调函数。你可以给二者传入一个依赖数组,该数组包含了组件作用域的值(比如 props 和 state),这些值将在 hooks 内部被用到。每次渲染时,React 都会比较这些依赖值,一旦它们发生改变,React 就会更新备忘的值或函数。

注意:React 为了尽可能快地进行比较,使用了比较算法Object.is 来优化比较的速度。也就是说,如果你把对象或数组的新实例作为 props 传入,比较时该算法会返回 false ,并重新计算备忘的值。

传入备忘的 props

在本例中,未经使用 React.memo 的过滤器组件需要优化。这曾是父组件设置 props 的方式:

function handleChange(value => {
  ...
});

<RangeSlider  
  value={[minValue, maxValue]}  
  onChange={handleChange}
/>
复制代码

每渲染一次,都要创建 handleChange 的一个实例,并传入一个新的数组实例作为 props。这就导致 RangeSlider 组件总是更新,尽管有 React.memo 包裹,因为 Object.is() 比较算法总是返回 false 。为了精确优化,我得用下列代码重构:

const handleChange = useCallback((value) => {
    ...
}, []);

const value = useMemo(() => [minValue, maxValue], [minValue, maxValue]);

<RangeSlider  
  value={value}  
  onChange={handleChange}
/>
复制代码

如果依赖数组为空,那么 handleChange 则仅在挂载时更新。无论 minValuemaxValue 何时更改, value 总会返回一个新数组。

我对 Histogram 组件做了同样的优化, Histogram 组件把 props 传到 HistogramBuckets 子组件中。

小提示:要想快速找出两次渲染中哪些 props 发生了变化,可以用这个精巧的 hooks:useWhyDidYouUpdate。

成果

通过方便快捷的优化,组件的性能得到了显著提升。经过备忘优化后,在相同的操作下, Histogram 组件的渲染时间缩短到了 0.5 毫秒。比起原来的 72.7 毫秒加上第二个直方图消耗的 402.8 毫秒, 这可是超过千倍的提速啊! 最终成果就是,仅用了极小的努力,就获得了更流畅的用户体验。

[译] 用 React Hooks 和调试工具提升应用性能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

CSS实战手册(第2版)

CSS实战手册(第2版)

[美] David Sawyer McFarland / 俞黎敏 / 电子工业出版社 / 2010-6 / 69.80元

本书从介绍最基本的CSS知识开始,到建立用于打印网页的CSS和改进你的CSS习惯的最佳实践。将关于CSS的选择器、继承、层叠、格式化、边距、填充、边框、图片、网站导航、表格、表单、浮动布局、定位网页上的元素,以及用于打印网页的CSS等技术通过逐步地讲解与教程串联了起来。每章内容从简单到复杂,一步一步地建立起一个完整的教程示例,并在每章都会详细讨论一些技巧、最佳实践和各浏览器之间一致性的兼容问题及如......一起来看看 《CSS实战手册(第2版)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码