作者 | Addy Osmani
譯者 | 許學文
策劃 | 蔡芳芳
轉發鏈接:https://mp.weixin.qq.com/s/ciIl4Cg9ZULDUY77Yuot0A
本文最初發布于Addy Osmani博客,經原作者授權由 InfoQ 中文站翻譯并分享
今天我將向大家演示如何使用 React Profiler API、Tracing API 以及 User Timing API 來分別追蹤 React 的組件渲染、用戶交互以及自定義性能指標。
React Profiler API
首先來了解下 React Profiler,它主要用來追蹤應用組件的 渲染過程 以及渲染開銷,同時標記出應用的性能瓶頸。Profiler 接受一個 onRender 回調函數,當被追蹤的組件以及子代組件發生更新時,該函數就會被調用。下圖是在影片排期應用中使用 Profiler 追蹤各個組件渲染:

Profiler 中 onRender 回調函數的具體參數如下:
- id:: 這是 Profiler 的唯一標示,區分是哪個 Profiler 追蹤的組件樹發生了更新
- phase: 如果更新是掛載階段這個值就是“mount”,如果是二次渲染階段就是“update”
- act ualDuration: 更新花費的渲染時間
- baseDuration: 更新預計花費的渲染時間
- startTime: 更新開始時間點
- commitTime: 更新提交的時間點
- interactions: 更新中包含的交互信息
const callback = (id, phase, actualTime, baseTime, startTime, commitTime) => {
console.log(`${id}'s ${phase} phase:`);
console.log(`Actual time: ${actualTime}`);
console.log(`Base time: ${baseTime}`);
console.log(`Start time: ${startTime}`);
console.log(`Commit time: ${commitTime}`);
}
運行上面的代碼,在 Chrome 調試器中可以看到如下輸出:

也可以打開 React DevTools,在 Profiler 面板中可以看到組件渲染的時間火焰圖:

切換到排序視圖

當然也可以使用多個 Profiler 來分別追蹤應用中的各個不同的部分,示例代碼如下:
import React, { Fragment, unstable_Profiler as Profiler} from "react";
render(
<App>
<Profiler id="Header" onRender={callback}>
<Header {...props} />
</Profiler>
<Profiler id="Movies" onRender={callback}>
<Movies {...props} />
</Profiler>
</App>
)
知道了如何追蹤組件渲染,那么如果想跟蹤交互,該怎么做?
交互追蹤 Tracing API
想一下,如果能追蹤到交互(例如:按鈕的點擊),那么在回答“這個按鈕點擊花費了多少時間更新 DOM?”這樣的問題時是不是就有了依據。要感謝 Brian Vaughn 的努力,React 在其 調度包 中引入了對這個功能的試驗支持,更詳細的說明可以點擊 這里 查看。
一個交互追蹤,需要包含一個描述(例如:添加購物車按鈕被點擊)、一個時間戳和一個回調函數,在回調函數中你可以定義一些和該交互相關的邏輯。在“影片排期應用”中就有一個添加電影到播放列表的“+”號按鈕,這個就是一個交互按鈕。

下面的代碼演示了如何追蹤這個按鈕的點擊行為:
import { unstable_Profiler as Profiler } from "react";
import { render } from "react-dom";
import { unstable_trace as trace } from "scheduler/tracing";
class MyComponent extends Component {
addMovieButtonClick = event => {
trace("Add To Movies Queue click", performance.now(), () => {
this.setState({ itemAddedToQueue: true });
});
};
在 React 開發調試工具的 interaction 面板中可以看到具體的交互行為和持續時間:

這個 API 同樣也可以 追蹤初始化渲染:
import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () => {
ReactDom.render(<App />, document.getElementById("app"));
})

Brian 提供了更多的例子,比如如何追蹤異步行為等。這些示例都在其“React 中進行交互追蹤”項目的 gist 中。
Puppeteer 的使用
如果想對 UI 交互追蹤腳本做進一步了解的話,你可能會對 Puppeteer 這個庫感興趣。Puppeteer 是一個 Node 庫,基于 Chrome 開發協議封裝 API 來操作 headless Chrome(譯者注:Chrome 瀏覽器對無界面形態)。
為了捕獲 DevTools 對當前運行程序性能的追蹤,Puppeteer 提供了 trace .start() 和 trace.stop() 兩個 API,下面我們就用它來追蹤按鈕點擊的過程,代碼如下::
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('https://react-movies-queue.glitch.me/')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
const addMovieToQueueBtn = 'li:nth-child(3) > .card > .card__info > div > .button';
await page.waitForSelector(addMovieToQueueBtn);
// 開始追蹤...
await page.tracing.start({ path: 'profile.json' });
// 按鈕點擊
await page.click(addMovieToQueueBtn);
// 停止追蹤
await page.tracing.stop();
await browser.close();
然后在開發工具的性能面板中導入 profile.json,我們就可以看到當按鈕點擊的時候,所有函數的調用情況:

如果你對交互追蹤感興趣并且想了解更多的話,不妨看看 Stoyan Stefanov 的“JAVAScript 組件級別的 CPU 開銷”這篇文章。
客戶端性能追蹤 API
使用 客戶端性能追蹤 API 可以追蹤一些定制的性能指標,并且時間精確度會更高。它有 2 個主要的 API:
- window.performance.mark(): 存儲當前 mark 執行時的時間戳
- window.performance.measure(): 存儲 2 個相同 mark 之間的執行時間
示例代碼如下:
// 記錄任務開始之前的時間戳
performance.mark('Movies:updateStart');
// 這里執行了一些任務...
// 記錄任務結束的時間戳
performance.mark('Movies:updateEnd');
// 計算任務開始前后的差值
performance.measure('moviesRender', 'Movies:updateStart', 'Movies:updateEnd'
當你通過 Chrome 調試工具中的性能面板查看一個 React 應用時,有一個“Timings”的區域,這里歸集了你的 React 組件的執行時間。在渲染時,React 會把通過客戶端 API 得到的性能數據發布到這里。

注意:React 在它的開發包中用 Profiler 替代了 User Timings,不過由于 User Timings 的時間精度更高,所以可能會在未來的 3 級規格的瀏覽器中重新添加它。
在互聯網上,你會發現有一些其他的 React 應用已經在使用 User Timing 追蹤他們的 自定義指標,包括 Reddit 網站中的“到第一標題可見花費的時間”和 Spotify 網站中的“到回放準備完畢花費的時間”。

還可以在 Chrome 調試器的 Lighthouse 面板 中查看到定制化的 User Timing 標記和追蹤方法,如下圖:

在 Next.js 的最近版本中也針對一些事件 添加 了很多 User timing 標記和追蹤,例如:
- Next.js-hydration: 混合持續時間
- Next.js-nav-to-render: 導航開始到開始渲染之間的時間
所有的這些追蹤都可以在調試器的 Timings 區域看到:

對比 DevTools 和 Lighthouse
值得注意的是,Lighthouse 和 Chrome 調試工具 中的性能面板都可以深入分析 React 應用程序的加載和運行時性能,用戶可以看到下面這些性能指標:

React 用戶可能會喜歡像 總阻塞時間 (TBT) 這樣的新指標,它量化一個頁面具體什么時候才可以交互(可 交互時間), 下面我們可以看下在并發模式前后應用發生更新時,TBT 的情況:

這些工具一般能幫助我們了解在瀏覽器級別的視圖性能瓶頸,例如,哪些 繁重冗長的任務 會引起交互延遲 (例如按鈕點擊響應) :

Lighthouse 還為一些特定的性能場景提供了修改建議。如在 Lighthouse 6.0 中可以看到一個提示,建議我們移除 未使用的 JavaScript代碼。Lighthouse 追蹤到了這個問題并且提醒我們可以使用 React.lazy () 來引入這個 JavaScript。

借助用戶端的硬件進行性能智能檢查,往往對性能分析非常有幫助。
最后,除了上面提到的我通常還會從 RUM 和 CrUX 獲取一些數據字段,然后用 webpagetest.org/easy 工具幫我生成更多的場景圖片,以便更好的進行性能分析。