如何方便的检测React项目的性能?

开发 前端
React本身就内置了性能检测组件 —— Profiler,可以很方便的检测React项目的性能。

大家好,我卡颂。

对于长期迭代的React项目,性能是不能忽视的问题。通常我们通过:

  • React-Dev-Tools的Profiler面板
  • 一些第三方工具,比如why-did-you-render[1]

检测运行时性能瓶颈。

实际上,React本身就内置了性能检测组件 —— Profiler,可以很方便的检测React项目的性能。

使用方式

Profiler是个内置组件,用他包裹需要检测性能的组件即可:

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>

嵌套使用也是可以的:

<App>
  <Profiler id="Sidebar" onRender={onRender}>
    <Sidebar />
  </Profiler>
  <Profiler id="Content" onRender={onRender}>
    <Content />
  </Profiler>
</App>

Profiler会检测被他包裹的组件树的性能,检测结果会作为onRender回调的参数:

function onRender(
  id, 
  phase, 
  actualDuration, baseDuration, 
  startTime, commitTime
) {
  // ...回调
}

那么,这些参数都是什么意思呢?其实我们完全没必要记这些。

我们只需要知道,一些典型的性能优化场景该使用哪些参数就行。

场景1:组件是不是嵌套更新?

对于一般的组件更新,会经历4个步骤:

  1. 组件触发更新
  2. 计算更新的影响
  3. 执行DOM操作
  4. 视图更新

但如果在上一次更新流程的4个步骤还未走完的情况下,又触发新的更新:

可以发现,在这种情况下,「视图更新」的时机远滞后于一般更新流程,这会造成页面交互卡顿。

这就是「组件嵌套更新」,通常我们在useLayoutEffect中触发新的更新会遇到这种情况。

Profiler onRender回调的phase参数,用来表示组件所处更新阶段:

  • mount,代表组件是首屏渲染。
  • update,代表组件更新。
  • nested-update,代表组件嵌套更新。

通过该参数可以判断组件是否处于嵌套更新。

当遇到嵌套更新造成的性能问题,可以考虑用useEffect替代useLayoutEffect。

场景2:性能优化到底起没起作用?

当提到「性能优化」,很多同学第一反应就是:

  • useCallback
  • useMemo
  • React.memo

但当我们使用这些性能优化API后,我们怎么知道性能是否变得更好?

为了检测优化效果,通常会在关键组件打印个log,如果状态更新后log没打印,代表组件没有render,命中缓存成功,比如这样:

function App() {
  console.log('App render')
  // ...省略逻辑
}

但这样并不能反映性能优化的整体效果。这时候可以考虑Profiler中的actualDuration与baseDuration参数:

  • baseDuration衡量组件子树在不命中任何缓存时,完整render一次所花时间。
  • actualDuration衡量组件子树实际完整render一次所花时间。

所以,用baseDuration减去actualDuration剩余的时间,就是性能优化节约的时间。

比如,对下面的<App/>组件性能优化后,只需要在onRender中比较baseDuration与actualDuration之间的差就能度量<App />的性能优化效果:

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>

这个值越高,代表性能优化效果越好。当接近0时,代表性能优化没有起到作用。

需要注意的是,baseDuration是通过子树中每个组件最近render所需时间汇总求和得到的近似值,有时并不准确

如果你的同事固执的认为所有函数props都必须用useCallback包裹,所有变量props都必须用useMemo包裹,请用以上数据狠狠的和他讲道理。

场景3:项目的性能瓶颈在哪?

当我们要做性能优化时,首先应该明确 —— 项目的性能瓶颈在哪?此时,可以用Profiler划分几个「待比较区域」,再分别对比actualDuration。

比如,对于下面的应用:

<App>
  <Sidebar />
  <Content />
</App>

<Sidebar />和<Content />谁render更耗时?

此时可以用Profiler分别包裹这两个组件:

<App>
  <Profiler id="Sidebar" onRender={onRender}>
    <Sidebar />
  </Profiler>
  <Profiler id="Content" onRender={onRender}>
    <Content />
  </Profiler>
</App>

再分别在onRender中衡量actualDuration,值比较高的区域render更耗时。

这种方式定制性比较高。如果想更直观比较哪些组件render更耗时,可以使用React Dev Tools中Profiler面板的火炬图。

总结

Profiler是React内置的性能分析组件,用于度量其包裹的子树的渲染性能。

最后说个有意思的细节 —— 在官网Profiler部分[2]中只介绍了Profiler有onRender这个回调。

从Profiler源码看,他还存在:

  • onCommit回调
  • onPostCommit回调
  • onNestedUpdateScheduled回调

不知道为什么,他们没有在文档中提及。

参考资料

[1]why-did-you-render:https://www.npmjs.com/package/@welldone-software/why-did-you-render。

[2]官网Profiler部分:https://react.dev/reference/react/Profiler。

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2022-11-09 11:50:21

2023-04-07 15:12:46

ReactReact-Intl

2023-07-04 07:30:35

React开发样式

2010-04-13 14:43:37

Oracle性能检测

2024-05-13 08:16:59

React任务调度链表结构

2022-08-03 09:11:31

React性能优化

2020-03-11 20:38:13

JavaScript前端工具

2020-09-16 14:39:13

ReactJavaScript框架

2024-01-29 09:29:02

计算机视觉模型

2023-07-19 15:45:47

ReactDOM轻量级

2015-07-07 10:11:39

GitHub项目流行度

2023-04-14 09:01:23

2009-02-04 15:55:31

敏捷开发敏捷项目项目经理

2020-03-13 07:32:18

物联网项目物联网IOT

2021-04-14 08:20:46

Lighthouse工具性能检测

2022-10-31 11:09:48

物联网智能建筑

2021-08-27 14:26:06

开发技能React

2024-07-18 08:08:06

2020-07-13 10:26:41

CIOIT项目技术

2023-06-20 18:33:00

DOM框架React
点赞
收藏

51CTO技术栈公众号