调试 React 为何(重新)渲染组件
React以其使用虚拟 DOM (VDOM) 的性能而闻名。它只会触发真实 DOM 中发生变化的部分的更新。在我看来,了解 React 何时触发组件的重新渲染非常重要,这样才能调试性能问题并开发快速高效的组件。
阅读完本文后,您应该对 React 渲染机制的工作原理以及如何调试重新渲染问题有充分的了解。
目录
什么是渲染?
首先,我们需要了解 Web 应用程序上下文中的渲染意味着什么。
如果您在浏览器中打开一个网站,您在屏幕上看到的内容是由DOM(文档对象模型)描述的,并通过HTML(超文本标记语言)表示的。
W3C 文档对象模型 (DOM) 是一个与平台和语言无关的接口,允许程序和脚本动态访问和更新文档的内容、结构和样式。
如果 JSX 代码被转换,React 就会创建 DOM 节点。我们需要注意的是,真实的 DOM 更新速度很慢,因为它们会导致 UI 重绘。如果 React 组件过大或嵌套多层,这就会成为一个问题。每次重新渲染组件时,其 JSX 代码都会转换为 DOM 节点,这会耗费额外的计算时间和计算能力。这时,React 的虚拟 DOM 就派上用场了。
虚拟 DOM
React 使用虚拟 DOM (VDOM) 作为 DOM 之上的附加抽象层,从而减少真实 DOM 的更新。如果我们更改应用程序中的状态,这些更改将首先应用于 VDOM。React DOM 库用于高效地检查 UI 中哪些部分真正需要在真实 DOM 中进行视觉更新。此过程称为diffing,基于以下步骤:
- VDOM 通过应用程序中的状态变化进行更新。
- 新的 VDOM 与之前的 VDOM 快照进行比较。
- 仅更新真实 DOM 中发生变化的部分。如果没有发生任何变化,则不会更新 DOM。
关于此机制的更多详细信息,请参阅React 关于协调的文档。
什么导致了 React 中的渲染?
React 中的渲染由以下原因引起
- 改变状态
- 传递 props
- 使用Context API
React 非常谨慎,会“一次性”重新渲染所有内容。状态改变后不渲染可能会造成信息丢失,因此重新渲染是更安全的选择。
我在StackBlitz上创建了一个演示项目,我将在本文中使用它来演示 React 的渲染行为:
该项目包含一个父组件,该父组件基本上由两个子组件组成,其中一个组件接收 props,另一个不接收:
class Parent extends React.Component {
render() {
console.warn('RENDERED -> Parent');
return (
<div>
<Child />
<Child name={name} />
</div>
);
}
}
如您所见,每次调用组件的函数时,我们都会在控制台中记录一条警告消息。在我们的示例中,我们使用了函数式组件,因此整个函数的执行与类组件的函数render
类似。render
如果你看一下StackBlitz 演示的控制台输出,你会看到 render 方法被调用了三次:
- 渲染
Parent
组件 Child
即使没有 props 也可以渲染Child
使用name
来自 state 的值作为 prop进行渲染
如果您现在修改输入字段中的名称,我们会为每个新值触发一次状态更改。父组件中的每次状态更改都会触发子组件的重新渲染,即使它们没有收到任何 props。
这是否意味着每次调用函数时,React 都会重新渲染真实的 DOM render
?不是,React 只会更新 UI 中发生变化的部分。每次组件状态被修改时,React 都会调度一次渲染。例如,通过setState
hook 更新状态不会立即发生,但 React 会在最佳时机执行。
但即使真实的 DOM 没有重新渲染,调用该render
函数也会产生一些副作用:
- 每次都会执行渲染函数内的代码,根据其内容,这可能会很耗时
- 对每个组件执行 diffing 算法,以确定 UI 是否需要更新
可视化渲染
可以在 Web 浏览器中可视化 React 的 VDOM 以及原生 DOM 渲染。
要显示 React 的虚拟渲染,您需要在浏览器中安装React DevToolsComponents -> View Settings -> Highlight updated when component render
。然后您可以在 下启用此功能。这样,我们就能看到 React 何时调用组件的 render 方法,因为它会高亮显示该组件的边框。这类似于我的演示应用程序中的控制台日志。
现在我们想看看真实 DOM 中更新了什么,因此我们可以使用 Chrome DevTools。通过 打开它F12
,转到右侧的三个点菜单并选择More tools -> Rendering -> Paint flashing
:
调试组件渲染的原因
在我们的小示例中,分析导致组件渲染的操作相当容易。但在大型应用程序中,由于组件往往更加复杂,因此分析起来会更加棘手。幸运的是,我们可以使用一些工具来帮助我们调试导致组件渲染的原因。
React DevTools
我们可以再次使用React DevTools的 Profiler 功能。此功能会记录分析期间每个组件的渲染原因。您可以在 React DevTools 的 Profiler 选项卡中启用它:
如果我们现在开始分析,触发状态改变,并停止分析,我们可以看到以下信息:
但是可以看到,我们只得到了组件因为hook触发的状态改变而渲染的信息,却不知道这个hook为什么引发了渲染。
你为什么要渲染?
要调试为什么钩子会导致 React 组件渲染,我们可以使用 npm 包Why Did You Render。
它对 React 进行 monkey patch 来通知您有关可避免的重新渲染。
因此,跟踪某个组件何时以及为何重新渲染非常有用。
我在StackBlitz上的演示项目中包含了 npm 包,要启用它,您需要在组件内启用它Parent.jsx
:
Parent.whyDidYouRender = true;
如果我们现在通过切换“切换上下文 API”复选框来触发父级重新渲染,我们可以看到来自库的其他控制台日志:
控制台输出为:
{Parent: ƒ}
Re-rendered because the props object itself changed but its values are all equal.
This could have been avoided by making the component pure, or by preventing its father from re-rendering.
More info at http://bit.ly/wdyr02
prev props: {} !== {} :next props
{App: ƒ}
Re-rendered because of hook changes:
different objects. (more info at http://bit.ly/wdyr3)
{prev : false} !== {next : true}
从输出中可以看到,我们获得了有关导致重新渲染的原因(例如,是否是道具或钩子变化)以及比较了哪些数据的详细信息,例如,哪些道具和状态用于差异。
结论
在本文中,我解释了 React 重新渲染组件的原因,以及如何可视化和调试此行为。在撰写本文和构建演示应用程序的过程中,我学到了很多东西。我希望您能够更好地理解 React 渲染的工作原理,并知道如何调试重新渲染问题。
将来,我会写更多关于 React 的文章,所以请在Twitter上关注我以获取最新文章的通知。
文章来源:https://dev.to/mokkaapps/debug-why-react-re-renders-a-component-3i8e