React.js 性能分析

2021-03-02 前端之巔

本文最初發布於Addy Osmani博客,經原作者授權由 InfoQ 中文站翻譯並分享

今天我將向大家演示如何使用 React Profiler APITracing API 以及 User Timing API 來分別追蹤 React 的組件渲染、用戶交互以及自定義性能指標。下面我會用一個影片排期的應用做具體的演示(譯者註:應用效果圖如下)。

首先來了解下 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>
)

知道了如何追蹤組件渲染,那麼如果想跟蹤交互,該怎麼做

想一下,如果能追蹤到交互(例如:按鈕的點擊),那麼在回答「這個按鈕點擊花費了多少時間更新 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 面板中可以看到具體的交互行為和持續時間:

import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () => {
   ReactDom.render(<App />, document.getElementById("app"));
})


Brian 提供了更多的例子,比如如何追蹤異步行為等。這些示例都在其「React 中進行交互追蹤」項目的 gist 中。

如果想對 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 可以追蹤一些定製的性能指標,並且時間精確度會更高。它有 2 個主要的 API:

// 記錄任務開始之前的時間戳
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 標記和追蹤,例如:

所有的這些追蹤都可以在調試器的 Timings 區域看到:

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

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

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

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

藉助用戶端的硬體進行性能智能檢查,往往對性能分析非常有幫助。

最後,除了上面提到的我通常還會從 RUM 和 CrUX 獲取一些數據欄位,然後用 webpagetest.org/easy 工具幫我生成更多的場景圖片,以便更好的進行性能分析。

《React 的 Profiler 介紹》

https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html

《React Profiler 分析器》

https://gist.github.com/bvaughn/60a883af01716a03a1b3285a1029be0c

《生產環境中如何在 react-dom 中使用 Profiler》

https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977

《React 交互追蹤》

https://gist.github.com/bvaughn/8de925562903afd2e7a12554adcdda16

《測量 React 應用性能》

https://www.debugbear.com/blog/measuring-react-app-performance

《漸進式 React》

https://houssein.me/progressive-react

《自定義測量指標》

https://web.dev/custom-metrics/

如果你對文章中的 demo 有興趣,可以點擊查看在線 demo,或者從 Glitch 上下載源碼。

https://addyosmani.com/blog/profiling-react-js/

相關焦點

  • 使用React、Electron、Dva、Webpack、Node.js、Websocket快速構建跨平臺應用
    Electron相當於給React生成的單頁面應用套了一層殼,如果涉及到文件操作這類的複雜功能,那麼就要依靠Electron的主進程,因為主進程可以直接調用Node.js的API,還可以使用C++插件,這裡Node.js的牛逼程度就凸顯出來了,既可以寫後臺的CRUD,又可以做中間件,現在又可以寫前端。
  • RegularJS —— 來自網易的JavaScript MVC框架
    在angular大行其道的時期也激勵產生了很多框架,比如vue.js、avalon.js、reactive等等優秀的框架,它們解決了一些問題, 比如avalon.js利用defineProperty實現了數據get-set的代理 並利用VB實現了ie6的兼容(當然數組還是wrap), 但總體來講基於dom實現的新秀們還是缺少足夠的差異化(代碼量的減少並不是最核心的部分)新思維的出現
  • 聊一聊前端性能與體驗的優化
    前言性能優化 ,每個工程師跑不掉的一個話題。這裡是本人總結的一些優化手法,希望對大家有所幫助,後續也會繼續更新。演示源碼和 PPT 無條件分享。演示 PPT (一定要看,超帥)橫屏觀看更佳:http://118.25.49.69:8086前端性能的影響前端性能的一個重要指標是頁面加載時間,不僅事關用戶體驗,也是搜尋引擎排名考慮的一個因素。
  • React Native痛點解析之性能調優
    接下來介紹下實踐中遇到的一些性能問題以及優化方案。以下對性能參數的依據是來自於React Native自帶的FPS Monitor.場景: 在Navigator還沒出來時,導航器是由NavigatorIOS來實現的,當時覺得頁面切換動畫很流暢,但是一旦用Navigator後,發現定義的切換動畫會使JS線程出現嚴重的掉幀(卡頓現象)。
  • 雲音樂 React Native 體系建設與發展
    /node_modules/react-native/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js    sed -i -e 's/function normalizePrefix(moduleName: string)/const normalizePrefix = function(moduleName
  • 【英】新版 React DevTools 簡介
    react-domreact-nativeHow do I get the new DevTools?React DevTools is available as an extension for Chrome and Firefox.
  • 2019 React Redux 完全指南
    actions.jsexport const INCREMENT = "INCREMENT";export const DECREMENT = "DECREMENT";然後你就可以引入這些 action 名稱,用它們來代替手寫字符串:Counter.jsimport React from "react";import
  • clipboard.js 的源碼分析
    2020 年即將結束了,不知不覺 源碼分析 專題已經寫了 9 篇文章,往期的 8 篇文章介紹了 Axios、BetterScroll、koa-compose 和 FileSaver.js 等優秀的開源項目,該專題的每篇文章阿寶哥都花了挺多時間與精力。
  • 我發現了 Vue.js 中的性能陷阱
    問題出在 Vue.js 嗎?是 Netlify 嗎?還是因為我們的代碼有缺陷?我必須找出答案。 我內心深處對遊戲的熱愛,讓我一直渴望能自己製作一些電子遊戲。幾個月前我開始將這種夢想變為現實,並第一次參加了全球遊戲大賽(Global Game Jam)。我和我的團隊使用 Vue.js 構建了一個名為「 ZeroDaysLeft 」的遊戲,其形式是 Web 端的單頁面應用程式。
  • 前端高效開發必備的 js 庫梳理
    本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫之前有很多人問學好前端需要學習哪些 js 庫, 主流框架應該學 vue 還是 react ? 針對這些問題, 筆者來說說自己的看法和學習總結.
  • 精通react/vue組件設計之配合React Portals實現一個(Drawer)組件
    通過組件的設計過程,大家會接觸到一個完成健壯的組件設計思路和方法,也能在實現組件的過程逐漸對react/vue的高級知識和技巧有更深的理解和掌握,並且在企業實際工作做遊刃有餘.正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先看看實現後的組件效果:1. 組件設計思路按照之前筆者總結的組件設計原則,我們第一步是要確認需求.
  • React 靈魂 23 問,你能答對幾個?
    為了降低時間複雜度,react 的 diff 算法做了一些妥協,放棄了最優解,最終將時間複雜度降低到了 O(n)。那麼 react diff 算法做了哪些妥協呢?8、為什麼虛擬dom 會提高性能?虛擬dom 相當於在 JS 和真實 dom 中間加了一個緩存,利用 diff 算法避免了沒有必要的 dom 操作,從而提高性能。9、錯誤邊界是什麼?它有什麼用?在 React 中,如果任何一個組件發生錯誤,它將破壞整個組件樹,導致整頁白屏。
  • 精通react/vue組件設計之實現一個Tag(標籤)和Empty(空狀態)組件
    正文在開始組件設計之前希望大家對css3和js有一定的基礎.我們先看看實現後的組件效果:由圖可以知道tag組件可以自定義顏色主題(color theme), 可以手動關閉標籤, 空狀態主要是提供用戶數據展示用的, 實現起來很簡單,重點在圖標的使用上.
  • D3.js、echar.js 前端必備大數據技能
    ,因為那時候沒有svg、canvas的概念,但是flash成本較高,圖表多的時候很耗性能。後來越來越多的js框架出現,得益於HTML5的api的推廣和完善,把可視化發揮的淋漓盡致,其中不乏佼佼者老外的D3.js、highcharts、百度的echarts等那今天北媽就來捋一捋這幾個用的最頻繁的可視化js庫的特點和選擇問題。
  • 《精通react/vue組件設計》之快速實現一個可定製的進度條組件
    每一個組件只負責某一特定的表現或者功能)正文在開始組件設計之前希望大家對css3和js有一定的基礎.我們先看看實現後的組件效果:上圖可以知道封裝後的進度條組件通過對外暴露的接口(react/vue裡面可以看做
  • 使用reveal.js製作精美的網頁版PPT
    接下來我們再看看reveal.js的優勢.reveal.js使介紹以及核心api作為一名前端工程師, 我們很容易把reveal.js集成到我們的vue或者react項目中, 但是作為演講類型的項目,我們直接用最原始的方式實現即可,首先我們需要引入相關的文件,具體可參考官網所說的步驟去做: revealjs.com/.
  • 前端進階: 總結幾個常用的js搜索算法和性能對比
    前言今天讓我們來繼續聊一聊js算法,通過接下來的講解,我們可以了解到搜索算法的基本實現以及各種實現方法的性能
  • 《精通react/vue組件設計》之實現一個健壯的警告提示(Alert)組件
    正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先看看實現後的組件效果:1. 組件設計思路按照之前筆者總結的組件設計原則,我們第一步是要確認需求.>內置提供不同類型的警告提示樣式,比如成功, 錯誤, 警告等關閉提示時能提供自定義事件需求收集好之後,作為一個有追求的程式設計師, 會得出如下線框圖:對於react
  • 從前端性能優化引申出來的5道經典面試題
    上面說的幾個並行插件可能在某些情況下達不到你想要的效果,然而在我們團隊優化webpack性能經驗來看,這次所說的兩個插件是很明顯並且每次都能提高打包速度的。原理就是先把第三方依賴先打包一次生成一個js文件,然後真正打包項目代碼時候,會根據映射文件直接從打包出來的js文件獲取所需要的對象,而不用再去打包第三方文件。
  • 高清完整:React.js框架從入門到精通【百度雲好課分享】
    高清完整:React.js框架從入門到精通【百度雲好課分享】不用懷疑!