如何方便的檢測(cè)React項(xiàng)目的性能?
大家好,我卡頌。
對(duì)于長(zhǎng)期迭代的React項(xiàng)目,性能是不能忽視的問(wèn)題。通常我們通過(guò):
- React-Dev-Tools的Profiler面板
- 一些第三方工具,比如why-did-you-render[1]
檢測(cè)運(yùn)行時(shí)性能瓶頸。
實(shí)際上,React本身就內(nèi)置了性能檢測(cè)組件 —— Profiler,可以很方便的檢測(cè)React項(xiàng)目的性能。
使用方式
Profiler是個(gè)內(nèi)置組件,用他包裹需要檢測(cè)性能的組件即可:
<Profiler id="App" onRender={onRender}>
<App />
</Profiler>
嵌套使用也是可以的:
<App>
<Profiler id="Sidebar" onRender={onRender}>
<Sidebar />
</Profiler>
<Profiler id="Content" onRender={onRender}>
<Content />
</Profiler>
</App>
Profiler會(huì)檢測(cè)被他包裹的組件樹的性能,檢測(cè)結(jié)果會(huì)作為onRender回調(diào)的參數(shù):
function onRender(
id,
phase,
actualDuration, baseDuration,
startTime, commitTime
) {
// ...回調(diào)
}
那么,這些參數(shù)都是什么意思呢?其實(shí)我們完全沒必要記這些。
我們只需要知道,一些典型的性能優(yōu)化場(chǎng)景該使用哪些參數(shù)就行。
場(chǎng)景1:組件是不是嵌套更新?
對(duì)于一般的組件更新,會(huì)經(jīng)歷4個(gè)步驟:
- 組件觸發(fā)更新
- 計(jì)算更新的影響
- 執(zhí)行DOM操作
- 視圖更新
但如果在上一次更新流程的4個(gè)步驟還未走完的情況下,又觸發(fā)新的更新:
可以發(fā)現(xiàn),在這種情況下,「視圖更新」的時(shí)機(jī)遠(yuǎn)滯后于一般更新流程,這會(huì)造成頁(yè)面交互卡頓。
這就是「組件嵌套更新」,通常我們?cè)趗seLayoutEffect中觸發(fā)新的更新會(huì)遇到這種情況。
Profiler onRender回調(diào)的phase參數(shù),用來(lái)表示組件所處更新階段:
- mount,代表組件是首屏渲染。
- update,代表組件更新。
- nested-update,代表組件嵌套更新。
通過(guò)該參數(shù)可以判斷組件是否處于嵌套更新。
當(dāng)遇到嵌套更新造成的性能問(wèn)題,可以考慮用useEffect替代useLayoutEffect。
場(chǎng)景2:性能優(yōu)化到底起沒起作用?
當(dāng)提到「性能優(yōu)化」,很多同學(xué)第一反應(yīng)就是:
- useCallback
- useMemo
- React.memo
但當(dāng)我們使用這些性能優(yōu)化API后,我們?cè)趺粗佬阅苁欠褡兊酶茫?/p>
為了檢測(cè)優(yōu)化效果,通常會(huì)在關(guān)鍵組件打印個(gè)log,如果狀態(tài)更新后log沒打印,代表組件沒有render,命中緩存成功,比如這樣:
function App() {
console.log('App render')
// ...省略邏輯
}
但這樣并不能反映性能優(yōu)化的整體效果。這時(shí)候可以考慮Profiler中的actualDuration與baseDuration參數(shù):
- baseDuration衡量組件子樹在不命中任何緩存時(shí),完整render一次所花時(shí)間。
- actualDuration衡量組件子樹實(shí)際完整render一次所花時(shí)間。
所以,用baseDuration減去actualDuration剩余的時(shí)間,就是性能優(yōu)化節(jié)約的時(shí)間。
比如,對(duì)下面的<App/>組件性能優(yōu)化后,只需要在onRender中比較baseDuration與actualDuration之間的差就能度量<App />的性能優(yōu)化效果:
<Profiler id="App" onRender={onRender}>
<App />
</Profiler>
這個(gè)值越高,代表性能優(yōu)化效果越好。當(dāng)接近0時(shí),代表性能優(yōu)化沒有起到作用。
需要注意的是,baseDuration是通過(guò)子樹中每個(gè)組件最近render所需時(shí)間匯總求和得到的近似值,有時(shí)并不準(zhǔn)確
如果你的同事固執(zhí)的認(rèn)為所有函數(shù)props都必須用useCallback包裹,所有變量props都必須用useMemo包裹,請(qǐng)用以上數(shù)據(jù)狠狠的和他講道理。
場(chǎng)景3:項(xiàng)目的性能瓶頸在哪?
當(dāng)我們要做性能優(yōu)化時(shí),首先應(yīng)該明確 —— 項(xiàng)目的性能瓶頸在哪?此時(shí),可以用Profiler劃分幾個(gè)「待比較區(qū)域」,再分別對(duì)比actualDuration。
比如,對(duì)于下面的應(yīng)用:
<App>
<Sidebar />
<Content />
</App>
<Sidebar />和<Content />誰(shuí)render更耗時(shí)?
此時(shí)可以用Profiler分別包裹這兩個(gè)組件:
<App>
<Profiler id="Sidebar" onRender={onRender}>
<Sidebar />
</Profiler>
<Profiler id="Content" onRender={onRender}>
<Content />
</Profiler>
</App>
再分別在onRender中衡量actualDuration,值比較高的區(qū)域render更耗時(shí)。
這種方式定制性比較高。如果想更直觀比較哪些組件render更耗時(shí),可以使用React Dev Tools中Profiler面板的火炬圖。
總結(jié)
Profiler是React內(nèi)置的性能分析組件,用于度量其包裹的子樹的渲染性能。
最后說(shuō)個(gè)有意思的細(xì)節(jié) —— 在官網(wǎng)Profiler部分[2]中只介紹了Profiler有onRender這個(gè)回調(diào)。
從Profiler源碼看,他還存在:
- onCommit回調(diào)
- onPostCommit回調(diào)
- onNestedUpdateScheduled回調(diào)
不知道為什么,他們沒有在文檔中提及。
參考資料
[1]why-did-you-render:https://www.npmjs.com/package/@welldone-software/why-did-you-render。
[2]官網(wǎng)Profiler部分:https://react.dev/reference/react/Profiler。