Categories
程式開發

React.js性能分析


今天我將向大家演示如何使用React Profiler APITracing API以及User Timing API來分別追踪React的組件渲染、用戶交互以及自定義性能指標。

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

下面我會用一個影片排期的應用做具體的演示(譯者註:應用效果圖如下)。

React.js性能分析 1

React Profiler API

首先來了解下React Profiler,它主要用來追踪應用組件的渲染過程以及渲染開銷,同時標記出應用的性能瓶頸。 Profiler接受一個onRender回調函數,當被追踪的組件以及子代組件發生更新時,該函數就會被調用。下圖是在影片排期應用中使用Profiler追踪各個組件渲染:

React.js性能分析 2

Profiler中onRender回調函數的具體參數如下:

  • id:這是Profiler的唯一標示,區分是哪個Profiler追踪的組件樹發生了更新
  • phase:如果更新是掛載階段這個值就是“mount”,如果是二次渲染階段就是“update”
  • actualDuration:更新花費的渲染時間
  • 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.js性能分析 3

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

React.js性能分析 4

切換到排序視圖

React.js性能分析 5

當然也可以使用多個Profiler來分別追踪應用中的各個不同的部分,示例代碼如下:

import React, { Fragment, unstable_Profiler as Profiler} from "react";
render(
  
    
      
)

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

交互追踪Tracing API

想一下,如果能追踪到交互(例如:按鈕的點擊),那麼在回答“這個按鈕點擊花費了多少時間更新DOM?”這樣的問題時是不是就有了依據。要感謝Brian Vaughn的努力,React在其調度包中引入了對這個功能的試驗支持,更詳細的說明可以點擊這裡查看。

一個交互追踪,需要包含一個描述(例如:添加購物車按鈕被點擊)、一個時間戳和一個回調函數,在回調函數中你可以定義一些和該交互相關的邏輯。在“影片排期應用”中就有一個添加電影到播放列表的“+”號按鈕,這個就是一個交互按鈕。

React.js性能分析 6

下面的代碼演示瞭如何追踪這個按鈕的點擊行為:

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面板中可以看到具體的交互行為和持續時間:

React.js性能分析 7

這個API同樣也可以追踪初始化渲染

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

React.js性能分析 8

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,我們就可以看到當按鈕點擊的時候,所有函數的調用情況:

React.js性能分析 9

如果你對交互追踪感興趣並且想了解更多的話,不妨看看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.js性能分析 10

在互聯網上,你會發現有一些其他的React應用已經在使用User Timing追踪他們的自定義指標,包括Reddit網站中的“到第一標題可見花費的時間”和Spotify網站中的“到回放準備完畢花費的時間”。

React.js性能分析 11

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

React.js性能分析 12

Next.js的最近版本中也針對一些事件添加了很多User timing標記和追踪,例如:

  • Next.js-hydration:混合持續時間
  • Next.js-nav-to-render:導航開始到開始渲染之間的時間

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

React.js性能分析 13

對比DevTools和Lighthouse

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

React.js性能分析 14

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

React.js性能分析 15

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

React.js性能分析 16

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

React.js性能分析 17

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

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

更多文章

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

原文鏈接:Profiling React.js Performance