24

分析 React 组件的渲染性能

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=Mzg2NDAzMjE5NQ%3D%3D&%3Bmid=2247485881&%3Bidx=1&%3Bsn=68b410e32c488556700b72aada9843df
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

今天,我们介绍一下如何使用 React Profiler API 分析 React 组件的渲染性能。

QnuQf2n.jpg!web

出于演示目的,我们将使用一个电影排队 APP 。

The React Profiler API

React Profiler API 会分析渲染和渲染成本,以帮助识别应用程序中卡顿的原因。

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

Profiler 接受一个 onRender 回调函数,当被分析的渲染树中的组件提交更新时,就会调用它。

const Movies = ({ movies, addToQueue }) => (
  <Fragment>
    <Profiler id="Movies" onRender={callback}>

为了进行测试,让我们尝试使用 Profiler 来测量 Movies 组件各部分的渲染时间:

MZFF7f7.png!web

ProfileronRender 回调接收描述渲染内容和所花费时间的参数:

  • id : 生提交的 Profiler 树的 id。如果有多个 profiler,它能用来分辨树的哪一部分发生了“提交”。
  • phase : "mount" (首次挂载) 或 "update" (重新渲染),判断是组件树的第一次装载引起的重渲染,还是由 props、state 或是 hooks 改变引起的重渲染。
  • actualDuration : 次更新在渲染 Profiler 和它的子代上花费的时间。
  • baseDuration
    Profiler
    render
    
  • startTime : 本次更新中 React 开始渲染的时间戳。
  • commitTime :  本次更新中 React commit 阶段结束的时间戳。在一次 commit 中这个值在所有的 profiler 之间是共享的,可以将它们按需分组。
  • interactions : 当更新被制定时,“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 DevTools 控制台,查看下面的时间:

qEnUri.png!web

我们还可以打开 React DevTools ,转到 Profiler 选项卡并可视化我们的组件渲染时间。下面是火焰图视图:

qARB7rF.png!web

我也喜欢使用排名视图,该视图已排序,因此渲染时间最长的组件显示在顶部:

NZZbumE.png!web

也可以使用多个 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>
);

但是,如果要追踪交互行为怎么办?

交互跟踪API

如果我们可以追踪交互行为(例如单击UI)来回答比如 “单击此按钮需要多长时间才能更新DOM?” 之类的问题,那就太强大了。感谢 Brian VaughnReact 通过新的调度器包中的交互跟踪API对交互跟踪提供了实验支持。这里有更详细的记录。

交互带有一个注释(例如“单击添加到购物车按钮”)和一个时间戳。还应该为交互提供一个回调函数,你可以在其中执行与交互相关的工作。

在电影APP中,有一个 “将电影添加到队列” 按钮( + )。单击此交互将电影添加到你的观看队列:

y6ZjmmJ.jpg!web

以下是此交互的跟踪状态更新的示例:

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 DevTools 中看到它的持续时间:

Zzeiq2v.png!web

也可以使用交互跟踪API跟踪首次渲染,如下所示:

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

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

Puppeteer

对于 UI 交互的更深入的脚本跟踪,你可能对 Puppeteer 感兴趣。 Puppeteer 是一个 Node 库,它提供了一个高级API,用于通过 DevTools 协议控制无头浏览器。

它提供了了 tracing.start()/stop() 这些工具方法,以捕获 DevTools 工作的性能跟踪。下面,我们使用它来跟踪单击主按钮时发生的情况。

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

profile.json 加载到 DevTools Performance 面板中,我们可以通过单击按钮来查看所有由此产生的 JavaScript 函数调用:

riEzA3r.png!web

User Timing API

通过 User Timing API ,可以使用高精度时间戳来测量应用程序的自定义性能指标。 window.performance.mark() 存储带有相关名称的时间戳,而 window.performance.measure() 存储两个标记之间经过的时间。

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

当你使用 Chrome DevTools 性能面板配置一个React应用程序时,你会发现一个名为 Timings 的部分,里面存储了 React 组件的处理时间。渲染时, React 可以使用 User Timing API 发布此信息。

MfIFziI.png!web

注意:React从他们的开发包中删除了 User Timing API ,取而代之的是 React Profiler,它提供了更准确的计时。他们可能会在未来的3级浏览器中重新添加它。

在网上,你会发现一些 React 应用利用 User Timing API 来定义自己的自定义指标。其中包括 Reddit 的“显示第一个帖子标题的时间”和 Spotif y的“准备播放的时间”:

j6zUraM.png!web

自定义用户计时指标也可以方便地反映在 Chrome DevToolsLighthouse 面板中:

au2qEvj.png!web

Next.js 的最新版本还为许多事件添加了更多的用户计时标记和度量,包括:

  • Next.js-hydration
  • Next.js-nav-to-render

所有这些度量都显示在 Timing 区域中:

bQJjAvz.png!web

DevTools & Lighthouse

LighthouseChrome DevTools Performance 面板可用于深入分析 React 应用程序的负载和运行时性能,突出显示以用户为中心的关键指标:

aQraIja.png!web

React 用户可能会喜欢像总阻塞时间(TBT)这样的新指标,它量化了一个页面在变得具有可靠交互性之前的非交互性(变为交互性的时间)。下面我们可以看到一个应用程序的并发模式的TBT之前/之后的TBT,在此更好地分散更新:

jQnuYb.png!web

这些工具通常有助于获得一个浏览器级别的瓶颈视图,如延迟交互的长时间任务(如按钮点击响应),如下所示:

j2Afmmn.png!web

Lighthouse 还提供了许多为 React 特殊定制的审计:

eUzq2eV.png!web

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

轻点在看,支持作者:heart:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK