大家好,我卡颂。
对于长期迭代的React
项目,性能是不能忽视的问题。通常我们通过:
React-Dev-Tools
的Profiler
面板- 一些第三方工具,比如why-did-you-render
检测运行时性能瓶颈。
实际上,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个步骤:
- 组件触发更新
- 计算更新的影响
- 执行
DOM
操作 - 视图更新
但如果在上一次更新流程的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部分中只介绍了Profiler
有onRender
这个回调。
从Profiler
源码看,他还存在:
onCommit
回调onPostCommit
回调onNestedUpdateScheduled
回调
不知道为什么,他们没有在文档中提及。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。