作者 | Addy Osmani
譯者 | 許學文
策劃 | 蔡芳芳
本文最初發佈於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 同樣也可以 追蹤初始化渲染:
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 工具幫我生成更多的場景圖片,以便更好的進行效能分析。
作者 | Addy Osmani
譯者 | 許學文
策劃 | 蔡芳芳