如果你不优化你的应用程序的性能,冻结的网页,迟缓的用户体验,处理众多的组件,缓慢的渲染和不必要的重新渲染是你在构建React应用程序时可能遇到的常见问题。
本文将重点介绍性能优化工具,这些工具可以帮助你识别React应用程序中的潜在问题,从而实现完美的用户体验。这些工具包括Profiler API、React.memo() ,以及React开发者工具。
内容
剖析器API
Profiler API(不是Chrome Dev工具中的那个)是一个相对较新的React组件,由B. Vaughn开发。它提供了一种方法来跟踪你的组件被重新渲染的次数和渲染的 "成本",也就是重新渲染所影响的时间和资源。
有了它,你可以快速识别你的应用程序中可能需要通过记忆化来优化的缓慢和有缺陷的区域。
如何使用分析器API
Profiler API通常需要两个道具:id 和一个onRender 回调函数,该函数在被<Profiler /> 包装的组件被挂载或更新时收集时间度量。这是一个非常有效的工具,可以检测你的React应用中滞后的组件。
下面的代码片段显示了如何对一个Footer 组件进行剖析。
import React, { Profiler } from "react";
return(
<App>
<Profiler id="Footer" onRender={callback}>
<Footer {...props} />
</Profiler>
<Main {...props} />
</App>
);
你也可以使用多个<Profiler/> 组件来跟踪你的应用程序的不同部分。
return(
<App>
<Profiler id="Footer" onRender={callback}>
<Footer {...props} />
</Profiler>
<Profiler id="Main" onRender={callback}>
<Main {...props} />
</Profiler>
</App>
);
<Profiler /> 组件也可以以一种方式嵌套,你可以在同一棵树内访问不同的组件。
return(
<App>
<Profiler id="Panel" onRender={callback}>
<Panel {...props}>
<Profiler id="Main" onRender={callback}>
<Main {...props} />
</Profiler>
<Profiler id="PreviewPane" onRender={callback}>
<PreviewPane {...props} />
</Profiler>
</Panel>
</Profiler>
</App>
);
这些只是使用<Profiler /> 来跟踪你的应用程序的性能的不同方法。
onRender 的回调
<Profiler/> 需要一个onRender 方法作为道具。当被分析的树中的一个组件 "提交 "一个变化时,这个函数会运行。它获取关于渲染的内容和所花时间的信息。
function callback(id, phase, actualTime, baseTime, startTime, commitTime, interactions) {
// aggregate or log render timings...
}
现在,让我们来定义这些道具。
id道具用于识别剖析器的报告;如果你使用几个剖析器,这可以帮助你找出树的哪一部分被破坏了phase(mount/update)将报告组件树是第一次被挂载,还是基于道具、状态或钩子的变化而被重新渲染的actualTime是剖析器挂载或更新其下级的时间量baseTime是每个组件的最新渲染时间的持续时间startTime是剖析器开始测量其后代的挂载/渲染时间的时间戳。commitTime是提交更新的时间。所有的剖析器在提交中共享这个值,如果需要的话,可以对它们进行分组interactions是在安排更新时跟踪的一组 "交互",例如,当你安排 功能时setState
尽管追踪交互的API仍在试验阶段,但在确定更新的原因时,它可以变得非常有效。你可以在这里了解更多关于它的信息。
import React, { Profiler } from "react";
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}`);
};
return (
<>
<Profiler id="CurrencyInput" onRender={callback}>
<CurrencyInput
props={props}
/>
</Profiler>
</>
);
在上面的例子中,<Profiler/> 被包裹在CurrencyInput 组件中,使你能够在这个组件挂载时访问关于这个组件的一堆有用信息。在这种情况下,你可以在控制台访问这些信息。


这些参数将有助于确定哪个组件树拖累了你的应用程序,哪个组件表现良好。
剖析器API的使用情况
你可以在以下情况下使用<Profiler /> 。
- 你打算以编程方式检索特定应用程序组件的时间。
- 你想确定哪个特定的用户操作或后端请求导致渲染的速度极慢
- 你想为你的性能发现提供一个更有语义的背景
剖析器API的缺点
- 它不一定能给你提供像React DevTools Profiler那样多的渲染信息
- 它是有代价的;你的组件树中的每个Profiler实例都会带来微小的性能损失。
由于Profiler API对性能的影响很小,最近在生产构建中也被禁用。虽然这通常不是一个问题,因为大多数严重的性能问题在生产和开发构建中都会被注意到,但这并不总是这样。有时你可能想在生产捆绑上访问你的组件的渲染时间。
React.memo()
React.memo()是一个函数,在处理不必要的重新渲染时往往会派上用场。它有助于提高功能组件和钩子的渲染效率,确保React跳过组件的重新渲染,如果其道具在加载/更新时没有变化。memo 代表备忘化。
如何使用React.memo()
将你的函数组件包裹在React.memo() 函数中是最常见的使用方法。
const Stone = React.memo(() => {
return (
<div className="black-stone">
<Task />
</div>
);
});
考虑一下下面的例子。
const Component1 = () => {
console.log("Component 1 rendered")
return (
<>
<p>Component 1</p>
</>
)
}
const Component2 = () => {
console.log("Component 2 rendered")
return (
<div>
<p>Component 2</p>
</div>
)
}
const SampleApp = () => {
const [counter, setCounter] = React.useState(0)
return (
<div>
<div>Count: {counter}</div>
<Component1 />
<Component2 />
<button onClick={() => setCounter(counter + 1)}>increase count</button>
</div>
)
}
上面的代码是一个简单的React应用程序,包含两个组件:Component1 和Component2 ,它们被App 组件所容纳。每当渲染或重新渲染时,这两个组件会向控制台报告。
App 组件也有一个计数器状态变量,在点击增加计数按钮时动态变化,导致Component1 和Component2 重新渲染。

在点击按钮四次后(将计数增加到四次),你可以看到我们对每个组件都有五条日志:一条用于初始渲染,另外四条用于每次点击按钮时(即每次重新渲染这些组件时)。这是一个你可能倾向于忽略的性能问题,因为它在小项目中可能不会被注意到,但它在更大的项目中会发挥作用,使你的应用程序变得多余和缓慢。
好消息是,你可以通过React.memo() 快速解决这个问题。例如,如果你不希望Component1 ,在你点击按钮时重新渲染,这里有办法。
const Component1 = React.memo(function Component1(props) {
console.log("Component 1 rendered")
return (
<div>
<p>Component 1</p>
</div>
)
});
现在我们已经用React.memo() 包装了Component1 ,让我们回到控制台,看看这对我们的应用程序有什么影响。

正如你所看到的,当计数器增加时,Component1 不会再重新渲染,从而解决了我们的问题。
使用情况React.memo()
在以下情况下,最好使用React.memo() 。
- 组件的渲染时间超过了100ms
- 组件对同一组道具不断地重新渲染,这经常发生在父组件强迫其子组件渲染时
- 你正在处理具有大量UI组件的大型应用程序,重新渲染会导致用户明显的响应延迟(糟糕的用户体验)。
缺点是React.memo()
当性能优势无法评估时,避免使用React.memo() 。如果重新渲染我们的组件(有和没有这个高阶组件)的计算时间很小或不存在,那么利用React.memo() 是没有意义的。
虽然用React.memo 包括包装类组件是可行的,但这被认为是不好的做法,所以非常不鼓励这样做。相反,扩展PureComponent 类是更可取的,这是一种更干净的方式来记忆你的类组件。
作为一个经验法则,如果你不能衡量性能优势,就不要使用备忘化。
React开发者工具
React有一个Chrome DevTools扩展,叫做React Developer Tools。React开发工具有两个标签。 **
**组件和 **
**Profiler。
组件选项卡让你可以访问你的应用程序的组件层次结构和它的状态信息。它同时显示根React组件和页面上渲染的子组件。
Profiler标签有助于性能优化,因为它给你一个完美的应用结构和组件渲染时间的类比。


注意,你必须使用React v.16.5或更高版本才能访问React开发者工具。
使用剖析器
通过对React应用程序进行剖析,你可以很容易地获得所有必要的数据,说明应用程序的全面性能,这使得你可以用React.memo() 来优化它。
使用剖析器有三个简单的步骤。
- 点击剖析器标签上的记录按钮;这使它能够访问你的应用程序的活动和它的一般UI行为
- 像往常一样进行你的应用程序的常规操作(在这一点上,Profiler将收集应用程序重新提交的数据)。
- 再次点击 "记录"按钮,停止记录
解释结果
通常情况下,React的运行分为两个阶段:渲染阶段,决定要做哪些DOM变化,以及提交阶段,实际执行操作的地方。
React的剖析器整理你的应用程序的性能信息,并以提交的形式显示,如下图所示,用条形图表示。

这些图表有三种不同的形式。
火焰图
该图表示你的组件在一次提交中的当前状态。每个条形图代表你的应用程序中的一个组件,每个条形图的长度由其相应组件的渲染时间决定,所以你的组件的渲染时间越长,条形图就越长。

上面的应用程序显示,Router.Provider的渲染时间比Router.Consumer的长。你可以点击每个条形图来获得每个组件的具体提交信息。
你也可以通过观察条形图的颜色来了解每个组件的渲染时间。这些颜色的解释如下。
- 灰色:该组件在提交过程中未能渲染。
- 蓝绿色:相对而言,该组件的渲染时间较短
- 黄绿色:相对而言,该组件花费了更多的时间来渲染
- 黄色:该组件花费了最多的时间来渲染
排名图
该图按照每个组件的渲染或重新渲染时间的排名顺序显示结果。花费时间最长的组件在上面。
通过这个图表,你可以立即发现哪些组件拖慢了你的应用程序,哪些组件对页面重新加载影响最大。

在上面的示例应用程序中,Router的渲染时间是最长的。
组件图表
你可以通过双击任何代表该组件的条形图来访问一个组件的图表。这个图表提供了在剖析时间内你的组件的生命周期的信息。

正如我前面提到的,剖析器提供了有用的信息,如你的应用程序的运行时间、组件的重新渲染时间,以及提交信息。这些结果是必不可少的,因为它们以不同的方式向你提供了应用程序的概况,使你能够弄清楚哪些组件造成了长时间的渲染,并使用备忘录等技术来优化它们,以避免不必要的重新渲染。
在学会如何成功使用这个Chrome DevTools插件后,发现React应用中的性能问题是小菜一碟。
React开发工具的使用情况
你可以在以下情况下使用React Developer Tools。
- 你想通过图形可视化的方式来了解你的应用程序的性能,逐个组件进行分析
- 你想跟踪每个组件的渲染时间,以及哪些组件有可能导致你的应用程序滞后或冻结,特别是对于更大的应用程序来说。
- 你想在控制台中访问组件和状态信息
React开发工具的缺点
尽管Profiler让你访问你的应用程序的组件信息,但它并没有主动解决问题,它只是向你展示了它。你仍然需要积极地应用优化技术,如备忘录化,以提高你的应用程序的性能。
Memoization在React术语中是一种优化技术,其中子组件只有在父组件重新渲染时道具发生变化时才会重新渲染。如果道具保持不变,它将跳过
render方法并返回缓存的结果。
比较图
| React开发工具分析器 | 剖析器API() | React.memo() | |
|---|---|---|---|
| 它是做什么的? | 分析你的应用程序并返回结果的图形表示。 | 分析你的应用程序并产生结果的程序化表示。 | 它用于根据得到的结果,在必要时对组件进行备忘。 |
| 它是否显示组件的渲染时间? | 是的。 | 是的。 | 不显示 |
| 它在生产捆绑上有效吗? | 没有 | 没有 | 是的 |
纵观这些工具,React Dev Tools Profiler对我来说只是走在了前面,因为它很容易使用,有很好的文档,并且可以让你以一种类似图形的方式完全访问你的组件树结构。识别React应用程序中的性能 "障碍 "再容易不过了。
总结
本指南展示了一些可用于分析React应用程序和识别性能问题的工具。我们还讨论了记忆化以及如何使用React.memo() 函数来提高你的应用程序的整体性能。编码愉快!