本教程操作环境:windows10系统、react16版,适用于所有品牌电脑。
在 React 中渲染
什么是渲染?
如果我们想了解 React 渲染和重新渲染的工作方式,那么最好了解库幕后发生的事情。
渲染是可以在不同抽象级别上理解的术语。根据上下文,它的含义略有不同。无论如何,最终它描述了生成图像的过程。
首先,我们需要了解什么是 DOM(文档对象模型):“ W3C 文档对象模型(DOM)是一种平台和语言无关的接口,它允许程序和脚本动态访问和更新文档的内容,结构和样式。”
用简单的英语来说,这意味着 DOM 代表您在打开网站时在屏幕上看到的内容,并通过标记语言 HTML 表示。JavaScript 还具有一个 DOM,它表示为一个对象,其中根元素为 document。
您可以通过 DOM 编程界面使用 JavaScript 修改 DOM,该界面包含 document.write,Node.appendChild 或等功能 Element.setAttribute。
什么是 VDOM?
然后,我们有了 React 的虚拟 DOM(或 VDOM),它是其之上的另一个抽象层。它由您的 React 应用程序的元素组成。
您应用程序中的状态更改将首先应用于 VDOM。如果 VDOM 的新状态需要更改 UI,ReactDOM 库将通过尝试仅更新需要更新的内容来有效地做到这一点。
例如,如果仅元素的属性发生变化,React 将仅通过调用 document.setAttribute(或类似方法)来更新 HTML 元素的属性。
红点表示 DOM 树的更新。
更新 VDOM 不一定会触发真实 DOM 的更新。
更新 VDOM 后,React 会将其与 VDOM 的先前快照进行比较,然后仅更新实际 DOM 中已更改的内容。如果没有任何变化,则完全不会更新真实的 DOM。 比较旧的 VDOM 和新的 VDOM 的过程称为 diffing。
实际的 DOM 更新速度很慢,因为它们会导致 UI 的实际重绘。React 通过更新实际 DOM 中可能的最小数量来提高效率。
因此,我们必须意识到本地和虚拟 DOM 更新之间的区别。
在 React 的关于对帐的文档中阅读有关此工作原理的更多信息。
这对性能意味着什么?
当我们在 React 中谈论渲染时,实际上是在谈论 render 函数的执行,这并不总是意味着 UI 的更新。
我们来看一个例子:
在函数组件中,整个函数的执行与类组件中的 render 函数等效。
当父组件的状态发生变化(在本例中为 App)时,这两个 Tile 组件将重新渲染,即使第二个组件甚至没有收到任何道具。
这意味着该 render 函数被调用了 3 次,但是实际的 DOM 修改只在 Tile 显示消息的组件中发生一次:
红点再次代表渲染。
在 React 中,这意味着调用 render 函数,在真实的 DOM 中,这意味着重新绘制 UI。
好消息是您不必担心 UI 重绘的性能瓶颈。React 已经为您优化了。
坏消息是:左侧所有这些红点表示这些组件的渲染功能已执行。
这些渲染函数的执行有两个缺点:React 必须在每个组件上运行其差异算法,以检查是否应更新 UI。
这些渲染函数或函数组件中的所有代码将再次执行。
可以说,第一点并不那么重要,因为 React 可以非常有效地计算出差异。危险在于您编写的代码在每个 React 渲染上都会一遍又一遍地执行。
在上面的示例中,我们有一个非常小的组件树。但是想像一下,如果每个节点有更多的子代,而这些子代又可能具有子代成分,会发生什么。我们将看到如何优化这一点
React 什么时候重新渲染?
上面我们看到了导致用户界面重新绘制的原因,但是从什么开始调用 React 的 render 函数呢?
每当组件状态改变时,React 都会调度渲染。
计划渲染意味着这不会立即发生。React 将尝试为此找到最佳时机。
更改状态意味着当我们调用 setState 函数时,React 触发更新(在 React Hooks 中,您可以从获得此 fn useState)。这不仅意味着将调用组件的 render 函数,而且还意味着 将重新渲染其所有后续子组件,无论其 prop 是否已更改。
如果您的应用程序结构不良,那么您运行的 JavaScript 可能比您预期的要多得多,因为更新父节点意味着要运行所有子代的 render 功能。
在本文的下一部分,我们将看到一些技巧,可以帮助您避免这种开销。
如何优化重新渲染
低效重渲染的一个很好的例子是在高阶组件中控制输入字段时。
黄色数字 render 表示每个组件的功能已执行的次数:
即使我们仅更新了蓝色组件的状态,也触发了更多其他组件的渲染。
控制何时更新组件
React 为我们提供了一些功能来防止这些不必要的更新。
让我们来看看它们,之后,我将向您展示另一种更有效的改善渲染性能的方法。
React.memo
我以前已经放弃的第一个是 React.memo。我已经写了一篇更深入的文章,但总而言之,它是一个功能,可以防止在道具不变时渲染 React Hook 组件。
一个实际的例子如下:
React 类的等效项是使用 React.PureComponent。
shouldComponentUpdate
这个功能是 React 生命周期的功能之一,它允许我们通过告诉 React 什么时候更新类组件来优化渲染性能。
它的参数是组件的下一个 props 和下一个状态:shouldComponentUpdate(nextProps, nextState) {
// return true or false
}
这个函数很容易使用:返回 true 会导致 React 调用 render 函数,返回会 false 阻止这种情况。
设置键属性
在 React 中,执行以下操作非常普遍。找出问题所在:
在这里,我忘记了设置 key 属性。大多数短毛绒都会对此发出警告,但是为什么它如此重要?
在某些情况下,React 依靠 key 属性来识别组件并优化性能。
在上面的示例中,如果将事件添加到数组的开头,React 会认为第一个元素和所有后续元素均已更改,并将触发这些元素的重新渲染。我们可以通过向元素添加键来防止这种情况:
尽量避免将数组的索引用作键,并使用一些可标识内容的东西。
密钥仅在兄弟姐妹之间必须唯一。
组件的结构
改进重渲染的更好方法是对代码进行一些重组。
注意放置逻辑的位置。如果将所有内容都放在应用程序的根组件中 React.memo,那么世界上的所有功能都将无法帮助您解决性能问题。
如果您将其放置在距离数据使用位置较近的位置,则可能甚至不需要 React.memo。
查看示例的优化版本并输入一些文本:
您会看到,即使状态更新,其他组件也不会重新渲染。
我所做的唯一更改是将处理状态的代码移到单独的组件中:
如果您需要在应用程序的其他部分中使用状态,则可以通过使用 React Context 或 MobX 和 Redux 等替代方法来使用。
结论
我希望更好地了解 React 的渲染机制如何工作以及如何才能充分利用这一点。对于本文,我必须对该主题进行一些其他研究,以更好地了解 React 的渲染方式。