虚拟 DOM 是一个基本的 React 概念。如果您在过去几年编写过 React 代码,您可能听说过它。但是,你可能不明白它是如何工作的以及 React 为何使用它。
本文将介绍什么是虚拟 DOM,它在 React 中的好处,以及帮助解释这个概念的实用示例代码。
在本文中:
-
概念回顾:DOM 是什么?
-
重新渲染如何影响性能
-
React 中的重新渲染:为什么要使用虚拟 DOM
-
-
React 中的虚拟 DOM
-
React 如何实现虚拟 DOM
-
-
回顾虚拟 DOM 以及为什么在 React 中使用它
概念回顾:DOM 是什么?
要了解虚拟 DOM 并了解 React 实现它的原因,让我们刷新对实际浏览器 DOM 的了解。
每当在浏览器中加载 Web 文档(例如 HTML)时,就会以树状结构创建文档元素的基于对象的表示。这种对象表示称为文档对象模型,也称为 DOM。
由于其基于对象的特性,JavaScript 和其他脚本语言理解 DOM,并且可以交互和操作文档内容。例如,使用 DOM,开发人员可以添加或删除元素、修改其外观以及对 Web 元素执行用户操作。
DOM 查询和更新之类的 DOM 操作更轻,因此非常快。但是,为了使更新反映在网页上,必须重新渲染该页面。
重新渲染如何影响性能
重新渲染页面以反映 DOM 更新的成本很高,并且可能导致性能下降,因为浏览器必须重新计算 CSS,重新运行每个可见元素的布局,并重新绘制网页。
让我们用下面的 JavaScript 代码模拟一个重新渲染的页面:
const update = () => { const element = ` <h3>JavaScript:</h3> <form> <input type="text"/> </form> <span>Time: ${new Date().toLocaleTimeString()}</span> `; document.getElementById("root1").innerHTML = element; }; setInterval(update, 1000);
你可以在 CodeSandbox 上看到完整的代码。
表示文档的 DOM 树如下所示:
通过在代码中使用setInterval()回调,我们每秒都在渲染 UI 的状态。正如我们在下面的 GIF 中看到的,在指定的时间间隔之后,浏览器会重新渲染、运行布局和重新绘制网页,以及其他操作。
浏览器 DOM 没有机制来比较和对比已更改的内容并仅重新绘制该 DOM 节点(在本例中为渲染时间):
这种重新渲染在文本输入中很明显。正如我们所看到的,输入字段总是在设置的时间间隔后被清除。DOM 操作后在浏览器中的重新渲染过程会导致性能缺陷。
React 中的重新渲染:为什么要使用虚拟 DOM
众所周知,React 是一个基于组件的库。如果 state 或 prop 发生变化,或者它的父组件重新渲染,React 组件自然会重新渲染。
React 无法承受每次重新渲染后重新绘制所有 DOM 节点的成本。为了克服这一挑战,React 实现了虚拟 DOM 的概念。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
React 不是允许浏览器在每次重新渲染或 DOM 更新后重新绘制所有页面元素,而是使用虚拟 DOM 的概念来确定发生了哪些变化而不涉及实际 DOM,然后确保实际 DOM 只重新绘制必要的数据。
这个概念有助于 React 优化性能。
React 中的虚拟 DOM
React 中的虚拟 DOM 是实际 DOM 的“虚拟”表示。它只不过是为复制实际 DOM 而创建的对象。
与实际的 DOM 不同,虚拟 DOM 的创建成本很低,奇妙搜索App,搜遍全球ED2k网络资源,聚合30多个API资源接口!因为它不会写入屏幕。它仅可用作一种策略,以防止在重新渲染期间重绘不必要的页面元素。
看一下代表前面 JavaScript 示例的 React 版本的以下渲染代码:
// ... const update = () => { const element = ( <> <h3>React:</h3> <form> <input type="text" /> </form> <span>Time: {new Date().toLocaleTimeString()}</span> </> ); root.render(element); };
为简洁起见,我们删除了一些代码。你可以在 CodeSandbox 上看到完整的代码。
我们也可以用纯 React 编写 JSX 代码,如下所示:
const element = React.createElement( React.Fragment, null, React.createElement("h3", null, "React:"), React.createElement( "form", null, React.createElement("input", { type: "text" }) ), React.createElement("span", null, "Time: ", new Date().toLocaleTimeString()) );
请注意,您可以通过将 JSX 元素粘贴到babel repl 编辑器中来获得与 JSX 代码等效的 React 代码。
现在,如果我们在控制台中记录 React 元素:
const element = ( <> <h3>React:</h3> <form> <input type="text" /> </form> <span>Time: {new Date().toLocaleTimeString()}</span> </> ); console.log(element)
我们会有这样的东西:
如上所示,该对象是虚拟 DOM。
React 如何实现虚拟 DOM
当我们渲染用户界面时,会创建一个用于该渲染的虚拟 DOM 并将其保存在内存中。如果渲染中发生更新,React 会自动为更新创建一个新的虚拟 DOM 树。
为了帮助进一步解释这一点,让我们像这样直观地表示虚拟 DOM:
但是,不要忘记虚拟 DOM 只是一个代表 UI 的简单对象。屏幕上没有绘制任何内容,因此很容易创建。
来自 LogRocket 的更多精彩文章:
-
不要错过来自 LogRocket 的精选时事通讯The Replay
-
了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
-
使用 React 的 useEffect优化应用程序的性能
-
在多个 Node 版本之间切换
-
了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
-
探索 Tauri,一个用于构建二进制文件的新框架
-
比较NestJS 与 Express.js
在 React 创建新的虚拟 DOM 树后,它会使用差异算法将其与之前的快照进行比较,以确定哪些更改是必要的。
然后它使用一个名为 ReactDOM 的库来确保实际的 DOM 只接收和重绘更新的一个或多个节点。这个过程称为和解。
当 React 实现 diffing 算法时,它首先比较两个快照是否具有相同的根元素。
如果它们具有相同的元素,React 会继续并递归属性,然后是 DOM 节点的子节点。如果根元素是不同类型的——这在大多数更新中很少见——React 将销毁旧的 DOM 节点并构建一个新的 DOM 树。
如果我们检查我们的 React 渲染,我们将得到以下行为:
在每次渲染时,React 都有一个虚拟 DOM 树,它与之前的版本进行比较以确定哪些节点内容得到更新,并确保更新的节点与实际 DOM 匹配。
在上面的 GIF 中,我们可以看到只有状态发生变化的渲染时间会在每次重新渲染时重新绘制。
在下面的另一个示例中,我们渲染了一个简单的 React 组件,该组件在单击按钮后更新组件状态:
import { useState } from "react"; const App = () => { const [open, setOpen] = useState(false); return ( <div className="App"> <button onClick={() => setOpen((prev) => !prev)}>toggle</button> <div className={open ? "open" : "close"}> I'm {open ? "opened" : "closed"} </div> </div> ); }; export default App;
如前所述,更新组件状态会重新渲染组件。然而,如下所示,在每次重新渲染时,React 只知道更新类名和更改的文本。
请参阅CodeSandbox 上的代码和演示。
回顾虚拟 DOM 以及为什么在 React 中使用它
每当我们在 React 中操作虚拟 DOM 元素时,我们都会绕过直接操作实际 DOM 时所涉及的一系列操作。
这是可能的,因为使用虚拟 DOM,屏幕上不会绘制任何内容。此外,通过 diffing 算法,React 可以最终确定哪些更新是必要的,并且只更新真实 DOM 上的对象。
React 中虚拟 DOM 的概念无疑有助于降低重新渲染网页的性能成本,从而最大限度地减少重新绘制屏幕所需的时间。
这里有一个简单的类比来进一步巩固我们对虚拟 DOM 的知识:将操作虚拟 DOM 视为编辑结构设计或蓝图,而不是重建实际结构。
与每次发生更新时重建结构相比,编辑蓝图以包含更新非常便宜。当蓝图被修改和定稿时,我们就可以只包括对实际结构的更新。
结论
Virtual DOM 只不过是 React 用来优化应用程序性能的一种策略。它提供了一种机制,可以比较两个渲染树以了解究竟发生了什么变化,并且只更新实际 DOM 上必要的内容。
和 React 一样,Vue 和其他一些框架也采用了这种策略。但是,Svelte 框架提出了另一种方法来确保优化应用程序。相反,它将所有组件编译成独立的微型 JavaScript 模块,使脚本非常轻巧且运行迅速。