原文地址。带补充标示的地方是翻译过程中拓展的知识点。
今天,我们来看看如何使用 React
的 Profiler API
来测试 React
的渲染性能; 如何使用 React
实验性的 交互追踪 API
来追踪 React
的交互;如何使用 Timing
API
来测量自定义指标。
为了方便演示,我们将使用一个展示电影列表的应用。
React Profiler API
React
提供的 Profiler API
用于测量 渲染和渲染成本,以帮助我们定位应用程序缓慢的瓶颈。
import React, { Fragment, unstable_Profiler as Profiler} from "react";
Profiler
使用 onRender
回调作为一个 prop
,被分析的树中的组件每次提交更新时,这个回调都会被执行。
const Movies = ({ movies, addToQueue }) => (
<Fragment>
<Profiler id="Movies" onRender={callback}>
为了测试,让我们试着使用 Profiler
来测量部分 Movies
组件的渲染时间。像这样:
Profiler
的 onRender
回调接收一些参数,用于描述渲染的内容和渲染时间。这些参数如下:
id
: 提交更新的Profiler
树的 “id
“ 属性。phase
: “mount
“ (首次加载) 或 “update
“ (重现渲染)actualDuration
: 提交更新的渲染时间baseDuration
: 没有记忆化(memoization
)的情况下,渲染所有子节点的估时startTime
: React 开始渲染的时间commitTime
: React 完成渲染的时间interactions
: 引发更新的具体交互
补充:
Memoization
是一种将函数返回值缓存起来的空间换时间的方法。原理很简单,就是把函数的每次执行结果都放入一个键值对(数组也可以)中,在接下来的执行中,在键值对中如果有值,直接返回该值,没有才去执行函数体求值并缓存。现代JavaScript
中经常使用这种技术。React useMemo
就是通过memoization
来提高性能的。
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
控制台,应该可以看到以下时间:
我们也可以打开 React DevTools
,进入 Profiler
标签,直观地看到我们的组件的渲染时间。下面是火焰图的视图:
我也很喜欢使用 Ranked
视图,它是按顺序排列的,所以渲染时间最长的组件会显示在最上面。
你也可以使用多个 Profilers
来测量你的应用程序的不同部分。
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>
);
补充:
React devtools
的Profiler
功能 只支持React v16.5+
构建的应用的追踪。因为React 16.5
添加了对开发者工具的Profiler
插件的支持。
但是,如果你想进行交互追踪怎么办?
交互追踪 API
如果我们能够追踪交互(例如点击用户界面),以回答 “这个按钮的点击需要多长时间来更新DOM?”这样的问题,那将是非常强大的。感谢Brian Vaughn
,React
通过新的 scheduler
包中的交互追踪 API
对交互追踪提供了实验性支持。这里有更详细的记录。
交互被注释为一个描述(例如 “点击添加到购物车按钮”)和一个时间戳。交互也应该提供一个回调,在那里你可以做与交互有关的工作。
在我们的 “Movies
“ 应用程序中,我们有一个 “将电影添加到队列 “ 按钮(”+”)。点击这个按钮将电影添加到你的观看队列中。
下面是一个追踪这种交互的状态更新的例子。
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
中看到它的持续时间:
我们也可以使用交互追踪API来追踪初始渲染,如下所示:
import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () => {
ReactDom.render(<App />, document.getElementById("app"));
});
Brian
在他的 React gist
中涵盖了更多的交互追踪的例子,比如如何追踪异步交互。
补充:
Github
提供了一个非常有用的服务Gist
。开发人员可以使用Gist
记录他们的代码片段,但是Gist
不仅仅是为极客和码农开发的,每个人都可以用到它。
Puppeteer
对于更深入的 UI 交互脚本跟踪,您可能会对 Puppeteer
感兴趣。Puppeteer
是一个 Node
库,它提供了一系列 高级 API
,用于通过 DevTools
协议控制 无头 Chrome
浏览器。
它暴露了 trace.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
函数调用:
如果你有兴趣阅读更多关于这个主题的内容,请查看 Stoyan Stefanov
的文章 JavaScript组件级CPU成本。
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
的 Performance
面板分析 React
应用程序时,你会发现一个名为 “Timings
” 的部分,里面包含了你的 React
组件的处理时间。在渲染时,React
能够通过 User Timing API
发布该信息。
注意: React
正在从他们的 DEV
包中移除 User Timing
,以支持 React Profiler
,后者提供了更准确的计时。他们可能会在未来的3级浏览器中重新添加 User Timing
。
纵观整个 web
,你会发现 React
应用利用 User Timing
来定义自己的定制指标。其中包括 Reddit
的“Time to first post title visible
” 和 Spotify
的 “Time to playback ready
“:
补充:
Spotify
是世界上最大的音乐流媒体服务。
自定义的 User Timing
标记和度量也可以清晰的反映在 Chrome DevTools
的 Lighthouse
面板:
最近版本的 Next.js
还为一些事件添加了更多的 User Timing
标记和度量,包括:
- Next.js-hydration:hydration 时间。
- Next.js-nav-to-render:导航开始,直到呈现之前。
所有这些度量都将出现在 Timings
区域:
DevTools & Lighthouse
提醒一下, Lighthouse和DevTools Performance panel 面板 可用于深入分析 React
应用程序的加载和运行时性能, 突出关键以用户为中心的幸福指标:
React
用户可能会喜欢像Total Blocking Time
(TBT
)这样的新指标,它可以衡量一个页面从最初的不可响应变得具有可靠的响应性(Time to Interactive)的过程。下面我们可以看到使用 Concurrent
模式前后,TBT
的情况:
补充:
Concurrent
模式是一组React
的新功能,可帮助应用保持响应,并根据用户的设备性能和网速进行适当的调整。
这些工具通常有助于获得浏览器级的瓶颈视图,如延迟响应的繁重长任务(如按钮单击响应),如下所示:
Lighthouse
还提供了一些 React
特定的的审记指引。在 Lighthouse 6.0
中,您将看到一个 remove unused JavaScript audit 的审记,高亮提示可以使用 React.lazy()
动态引入这些已加载但未使用的 JavaScript
。
这总比在真实用户的硬件上对性能进行体检要好。我经常依靠webpagetest.org/easy和来自RUM和CrUX的现场数据来描绘一个更完整的画面。