React.js 性能分析

今天我将向大家演示如何使用 React Profiler APITracing API 以及 User Timing API 来分别追踪 React 的组件渲染、用户交互以及自定义性能指标。

本文最初发布于 Addy Osmani 博客,经原作者授权由 InfoQ 中文站翻译并分享。

下面我会用一个影片排期的应用做具体的演示(译者注:应用效果图如下)。

React Profiler API

首先来了解下 React Profiler ,它主要用来追踪应用组件的 渲染过程 以及渲染开销,同时标记出应用的性能瓶颈。Profiler 接受一个 onRender 回调函数,当被追踪的组件以及子代组件发生更新时,该函数就会被调用。下图是在影片排期应用中使用 Profiler 追踪各个组件渲染:

Profiler 中 onRender 回调函数的具体参数如下:

  • id: 这是 Profiler 的唯一标示,区分是哪个 Profiler 追踪的组件树发生了更新
  • phase: 如果更新是挂载阶段这个值就是“mount”,如果是二次渲染阶段就是“update”
  • actualDuration: 更新花费的渲染时间
  • baseDuration: 更新预计花费的渲染时间
  • startTime: 更新开始时间点
  • commitTime: 更新提交的时间点
  • interactions: 更新中包含的交互信息

复制代码

constcallback =(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(


)

知道了如何追踪组件渲染, 那么如果想跟踪交互,该怎么做

交互追踪 Tracing API

想一下,如果能追踪到交互(例如:按钮的点击),那么在回答“这个按钮点击花费了多少时间更新 DOM?”这样的问题时是不是就有了依据。要感谢 Brian Vaughn 的努力,React 在其 调度包 中引入了对这个功能的试验支持,更详细的说明可以点击 这里 查看。

一个交互追踪,需要包含一个描述(例如:添加购物车按钮被点击)、一个时间戳和一个回调函数,在回调函数中你可以定义一些和该交互相关的逻辑。在“影片排期应用”中就有一个添加电影到播放列表的“+”号按钮,这个就是一个交互按钮。

下面的代码演示了如何追踪这个按钮的点击行为:

复制代码

import{ unstable_Profiler asProfiler} from"react";
import{ render } from"react-dom";
import{ unstable_trace as trace } from"scheduler/tracing";
classMyComponentextendsComponent{
addMovieButtonClick = event => {
trace("Add To Movies Queue click", performance.now(), () => {
this.setState({ itemAddedToQueue:true});
});
};

在 React 开发调试工具的 interaction 面板中可以看到具体的交互行为和持续时间:

这个 API 同样也可以 追踪初始化渲染

复制代码

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

Brian 提供了更多的例子,比如如何追踪异步行为等。这些示例都在其“ React 中进行交互追踪 ”项目的 gist 中。

Puppeteer 的使用

如果想对 UI 交互追踪脚本做进一步了解的话,你可能会对 Puppeteer 这个库感兴趣。Puppeteer 是一个 Node 库,基于 Chrome 开发协议封装 API 来操作 headless Chrome(译者注:Chrome 浏览器对无界面形态)。

为了捕获 DevTools 对当前运行程序性能的追踪,Puppeteer 提供了 trace .start() 和 trace.stop() 两个 API,下面我们就用它来追踪按钮点击的过程,代码如下:

复制代码

constpuppeteer =require('puppeteer');
(async() => {
constbrowser =awaitpuppeteer.launch();
constpage =awaitbrowser.newPage();
constnavigationPromise = page.waitForNavigation();
awaitpage.goto('https://react-movies-queue.glitch.me/')
awaitpage.setViewport({ width: 1276, height: 689 });
awaitnavigationPromise;
constaddMovieToQueueBtn = 'li:nth-child(3) > .card > .card__info > div > .button';
awaitpage.waitForSelector(addMovieToQueueBtn);
// 开始追踪...
awaitpage.tracing.start({ path: 'profile.json' });
// 按钮点击
awaitpage.click(addMovieToQueueBtn);
// 停止追踪
awaitpage.tracing.stop();
awaitbrowser.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 应用已经在使用 User Timing 追踪他们的 自定义指标 ,包括 Reddit 网站中的“到第一标题可见花费的时间”和 Spotify 网站中的“到回放准备完毕花费的时间”。

还可以在 Chrome 调试器的 Lighthouse 面板 中查看到定制化的 User Timing 标记和追踪方法,如下图:

Next.js 的最近版本中也针对一些事件 添加 了很多 User timing 标记和追踪,例如:

  • Next.js-hydration: 混合持续时间
  • Next.js-nav-to-render: 导航开始到开始渲染之间的时间

所有的这些追踪都可以在调试器的 Timings 区域看到:

对比 DevTools 和 Lighthouse

值得注意的是, LighthouseChrome 调试工具 中的性能面板都可以深入分析 React 应用程序的加载和运行时性能,用户可以看到下面这些性能指标:

React 用户可能会喜欢像 总阻塞时间 (TBT) 这样的新指标,它量化一个页面具体什么时候才可以交互(可 交互时间 ), 下面我们可以看下在并发模式前后应用发生更新时,TBT 的情况:

这些工具一般能帮助我们了解在浏览器级别的视图性能瓶颈,例如,哪些 繁重冗长的任务 会引起交互延迟 (例如按钮点击响应) :

Lighthouse 还为一些特定的性能场景提供了修改建议。如在 Lighthouse 6.0 中可以看到一个提示,建议我们移除 未使用的 JavaScript 代码 。Lighthouse 追踪到了这个问题并且提醒我们可以使用 React.lazy () 来引入这个 JavaScript。

借助用户端的硬件进行性能智能检查,往往对性能分析非常有帮助。

最后,除了上面提到的我通常还会从 RUM 和  CrUX 获取一些数据字段,然后用 webpagetest.org/easy 工具帮我生成更多的场景图片,以便更好的进行性能分析。