精讀《React 性能調試》

1 引言

在數據中臺作 BI 工具常常面對海量數據的渲染處理,除了組件自己性能優化以外,常常要排查總體頁面性能瓶頸點,尤爲是維護一些性能作得並很差的舊代碼時。前端

React 性能調試是面對這種問題的必修課,藉助Profiling React.js Performance這篇文章一塊兒學習一下這個技能吧。react

2 精讀

本文介紹了衆多性能檢測工具與方法。git

React Profiler

Profiler這個 API 是一種運行時 Debug 的補充,能夠經過其 callback 拿到組件渲染信息,用法以下:github

const Movies = ({ movies, addToQueue }) => (
  <React.Profiler id="Movies" onRender={callback}>
    <div />
  </React.Profiler>
);

function callback(
  id,
  phase,
  actualTime,
  baseTime,
  startTime,
  commitTime,
  interactions
) {}

這個 callback 會在每次渲染時執行,渲染分爲初始化和更新階段,經過phase區分,下面是參數詳細說明:json

  • id: 傳入的 id。
  • phase: "mount" 或 "update",表示更新狀態。
  • actualDuration: 實際渲染耗時。
  • baseDuration: 沒有使用 memo 時的渲染預計耗時。
  • startTime: 開始渲染的時間。
  • commitTime: React 提交更新的時間
  • interactions: 何種緣由致使的渲染,好比setState或 hooks changed 之類。

注意儘可能不要輕易使用Profiler檢測性能,由於Profiler自己也會消耗性能。瀏覽器

若是不想得到這麼詳細的渲染耗時,或者不想提早在代碼中埋點,能夠利用 DevTools 的 Profiler 查看更直觀更簡潔的渲染耗時:性能優化

其中 Ranked 能夠展現按照渲染耗時排序後的結果,Interations 須要配合 Tracing API 使用,在後面會提到。服務器

Tracing API

利用scheduler/tracing提供的traceAPI,咱們能夠記錄某個動做的耗時,好比 「點擊添加按鈕收藏一個電影」 耗時多久:微信

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 });
    });
  };
}

在 Interations 中能夠看到動做觸發的耗時:網絡

這個動做還能夠是渲染,好比能夠記錄 ReactDOM 渲染的耗時:

import { unstable_trace as trace } from "scheduler/tracing";

trace("initial render", performance.now(), () => {
  ReactDom.render(<App />, document.getElementById("app"));
});

甚至還能夠追蹤異步的耗時:

import {
  unstable_trace as trace,
  unstable_wrap as wrap,
} from "scheduler/tracing";

trace("Some event", performance.now(), () => {
  setTimeout(
    wrap(() => {
      // 異步操做
    })
  );
});

有了Profilertrace這兩件武器,咱們能夠監控任意元素的渲染耗時與交互耗時,幾乎能夠涵蓋全部性能監控須要。

Puppeteer

咱們還能夠利用 Puppeteer 實現自動化操做並打印報告:

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);

  // Begin profiling...
  await page.tracing.start({ path: "profile.json" });
  // Click the button
  await page.click(addMovieToQueueBtn);
  // Stop profliling
  await page.tracing.stop();

  await browser.close();
})();

首先利用puppeteer建立一個瀏覽器,新建一個頁面並打開https://react-movies-queue.glitch.me/這個 URL,等待頁面加載完畢後利用 DOM 選擇器找到按鈕,利用page.clickAPI 模擬點擊這個按鈕,並在先後利用page.tracing記錄性能變化,並將這個文件上傳到 DevTools Performance 面板,就會獲得一份自動的性能檢測報告:

這張圖至關重要,是瀏覽器綜合運行開銷分析的利器,最上面分爲 4 個部分:

  • FPS:每秒幀數,綠色豎線越高表示 FPS 越高,出現紅線則表示出現了卡頓。
  • CPU:CPU 資源,用面積圖展現消耗 CPU 資源的事件。
  • NET:網絡消耗,每條橫槓表示一種資源的加載。
  • HEAP:內存水位,因爲短期內看不出來是否會內存溢出,通常只用來簡單看看內存消耗是否符合預期,對於內存溢出的檢測須要用持續監控上報的方式。

下面會有一張 Network 詳細圖解,好比這張圖:

細線表示等待的時間,粗線表示實際加載的狀況,其中淺色部分表示服務器等待時間,即從發送下載請求到服務器響應第一個字節的時間。這部分能夠看出資源並行加載阻塞狀況以及資源服務器響應時間是否存在問題。

Timings 展現了幾個重要時間節點,這裏列舉一部分:

  • FP:First Paint,第一次繪製。
  • FCP:First Contentful Paint,第一次內容繪製。
  • LCP:Largest Contentful Paint,最大內容繪製。
  • DCL:Document Content Loaded,DOM 內容加載完畢。

再下面是 JS 計算消耗,用了一張火焰圖,火焰圖是性能分析的經常使用可視化工具。如下面這張圖爲例:

看火焰圖首先看跨度最長的函數,也就是最長的那條線,這是最耗時的部分,從左到右是瀏覽器腳本的調用順序,從上到下是函數嵌套的順序。

咱們能夠看到鼠標位置的 34 這個函數雖然長,但並非性能瓶頸,由於下面執行的 n 函數長度和它同樣,表示 34 函數的性能幾乎無損耗,其性能由其調用的 n 函數決定。

咱們能夠利用這種方式一步步排查到葉子結點,找到對性能影響最大的元子函數。

User Timing API

咱們還能夠利用performance.mark自定義性能檢測節點:

// Record the time before running a task
performance.mark("Movies:updateStart");
// Do some work

// Record the time after running a task
performance.mark("Movies:updateEnd");

// Measure the difference between the start and end of the task
performance.measure("moviesRender", "Movies:updateStart", "Movies:updateEnd");

這些節點能夠在上面介紹的 Performance 面板中展現出來用於自定義分析。

3 總結

利用 Performance 進行通用性能分析,利用 React Profiler 進行 React 定製性能分析,這兩個結合在一塊兒幾乎能夠完成任何性能檢測。

通常來講,首先應該用 React Profiler 進行 React 層面的問題篩查,這樣更直觀,更容易定位問題。若是某些問題跳出了 React 框架範圍,或者再也不能以組件粒度進行度量,咱們能夠回到 Performance 面板進行通用性能分析。

討論地址是: 精讀《React 性能調試》 · Issue #247 · dt-fe/weekly

若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證
相關文章
相關標籤/搜索