深入了解 React 中的虚拟 DOM

简介: 深入了解 React 中的虚拟 DOM

深入了解 React 中的虚拟 DOM

虚拟 DOM 是 React 的一个基本概念。如果你在过去几年写过 React 代码,你可能听说过它。然而,你可能不理解它是如何工作的以及 React 为什么使用它。

本文将介绍什么是虚拟 DOM,它在 React 中的好处,以及帮助解释这个概念的实际示例代码。

1. 概念回顾:什么是 DOM

为了理解虚拟 DOM 并了解 React 实现它的原因,让我们回顾一下实际浏览器 DOM 的知识。

每当浏览器加载一个 web 文档(如 HTML)时,文档元素基于对象的表示就会以树状结构创建。这种对象表示称为文档对象模型,也称为 DOM。由于其基于对象的特性,JavaScript 和其他脚本语言理解 DOM,并可以交互和操作文档内容。例如,使用 DOM,开发人员可以添加或删除元素,修改它们的外观,并在 web 元素上执行用户操作。DOM 查询和更新等 DOM 操作更轻,因此非常快。然而,为了使更新反映在网页上,页面将不得不重新渲染。

2. 重新渲染如何影响性能

重新渲染页面以反映 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);

表示文档的 DOM 树如下所示:

de5f74bea7b1d49a590e7f6c9422e029.png

通过在代码中使用 setInterval() 回调函数,我们可以每秒渲染 UI 的状态。正如我们在下面的 GIF 中看到的,在指定的间隔后,浏览器重新渲染,运行布局,重新绘制网页,以及其他操作。

浏览器 DOM 没有机制来比较和对比已经更改的内容,只重绘 DOM 节点(在本例中是渲染时间):

ba3238338f309fe0c7f6edf97889441b.jpg

这种重新渲染在文本输入中很明显。正如我们所看到的,输入字段总是在设置的间隔之后被清除。DOM 操作之后浏览器中的重新渲染过程会导致性能不足。

3. React 中的重渲染:为什么使用虚拟 DOM

正如我们所知,React 是一个基于组件的库。如果 stateprop 发生变化,或者其父组件重新渲染,React 组件将自然地重新渲染。

React 无法承担每次重新渲染后重新绘制所有 DOM 节点的成本。为了克服这个挑战,React 实现了虚拟 DOM 的概念。

React 不允许浏览器在每次重新渲染或 DOM 更新后重新绘制所有页面元素,而是使用虚拟 DOM 的概念,在不涉及实际 DOM 的情况下找出究竟发生了什么变化,然后确保实际 DOM 只重新绘制必要的数据。这个概念帮助 React 优化性能。

4. React 中的虚拟 DOM

React 中的虚拟 DOM 是实际 DOM 的“虚拟”表示。它只是一个用于复制实际 DOM 的对象。与实际的 DOM 不同,虚拟 DOM 的创建成本很低,因为它不写入屏幕。它只能作为一种策略,以防止在重新渲染时重绘不必要的页面元素。

看看下面的渲染代码,它代表了前面 JavaScript 例子的 React 版本:

// ...
const update = () => {
 const element = (
  <>
   <h3>React:</h3>
   <form>
    <input type="text" />
   </form>
   <span>Time: {new Date().toLocaleTimeString()}</span>
  </>
 );
 root.render(element);
};

为了简洁起见,我们删除了一些代码。我们也可以在普通 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)

我们会得到这样的东西:

e1ba0c272788d9fba34160286b732fc8.png

如上所示,该对象是虚拟 DOM。

5. React 如何实现虚拟 DOM

当我们渲染用户界面时,为该渲染创建一个虚拟 DOM 并保存在内存中。如果在中渲染发生更新,React 会自动为更新创建一个新的虚拟 DOM 树。

为了帮助进一步解释这一点,让我们像这样直观地表示虚拟 DOM:

52ed3bc96b5deb3a5ba7c803aa27c9a2.png

但是,不要忘记虚拟 DOM 只是一个表示 UI 的简单对象。没有东西会被画在屏幕上,所以,它很容易创建。

在 React 创建新的虚拟 DOM 树之后,它将使用 diff 算法将其与前一个虚拟 DOM 树进行比较,以确定需要进行哪些更改。然后,它再确保实际的 DOM 只接收和重绘更新的节点。这个过程叫做 reconciliation。

efe43e268a2de65c63fc06c292d358d0.png

  • 当 React 实现 diff 算法时,它首先比较两个快照是否具有相同的根元素。如果它们具有相同的元素,则 React 继续向前并递归处理属性,然后是 DOM 节点的子节点。
  • 如果根元素是不同类型的,这在大多数更新中是罕见的,React 将销毁旧的 DOM 节点并构建一个新的 DOM 树。

如果我们检查我们的 React 渲染,我们将得到以下行为:

f71291071ad2644e5bba64841d53826a.jpg

在每次渲染时,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 只知道更新类名和更改的文本。

d65eaa3f2ff532f74d60a2106da36c8f.jpg

6. 虚拟 DOM 在 React 中使用的原因

每当我们在 React 中操作虚拟 DOM 元素时,我们都绕过了直接操作实际 DOM 时所涉及的一系列操作。这是可能的,因为使用虚拟 DOM,不会在屏幕上绘制任何东西。此外,通过 diff 算法,React 可以确定需要更新什么,只更新真正 DOM 上的对象。

React 中的虚拟 DOM 概念无疑有助于降低重新渲染网页的性能成本,从而将重新绘制屏幕所需的时间最小化。

这里有一个简单的类比,可以进一步巩固我们对虚拟 DOM 的知识:将操纵虚拟 DOM 看作是编辑结构设计或蓝图,而不是重新构建实际的结构。与每次发生更新时重新构建结构相比,编辑蓝图以包含更新非常便宜。当蓝图被修改和最终确定后,我们就可以只包含对实际结构的更新。

7. 小结

虚拟 DOM 只是 React 用来优化应用程序性能的一种策略。它提供了一种比较两个渲染树的机制,以了解究竟发生了什么变化,并且只更新实际 DOM 中必要的内容。

与 React 一样,Vue 和其他一些框架也采用了这种策略。然而,Svelte 框架提出了另一种方法来确保应用程序得到优化。相反,它将所有组件编译成独立的、微小的 JavaScript 模块,使脚本运行起来非常轻便和快速。


相关文章
|
2月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
39 2
|
3月前
|
JavaScript 前端开发 算法
react中虚拟dom和diff算法
在React中,虚拟DOM(Virtual DOM)和Diff算法是两个核心概念,它们共同工作以提高应用的性能和效率。
40 4
|
2月前
|
JavaScript 前端开发
react学习(3)创建虚拟dom的两种方式
react学习(3)创建虚拟dom的两种方式
167 67
|
1月前
|
JavaScript 前端开发 算法
React 虚拟 DOM 深度解析
【10月更文挑战第5天】本文深入解析了 React 虚拟 DOM 的工作原理,包括其基础概念、优点与缺点,以及 Diff 算法的关键点。同时,分享了常见问题及解决方法,并介绍了作者在代码/项目上的成就和经验,如大型电商平台的前端重构和开源贡献。
53 3
|
2月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
26 2
|
2月前
|
JavaScript 前端开发
react字符串转为dom标签,类似于Vue中的v-html
本文介绍了在React中将字符串转换为DOM标签的方法,类似于Vue中的`v-html`指令,通过使用`dangerouslySetInnerHTML`属性实现。
87 0
react字符串转为dom标签,类似于Vue中的v-html
|
1月前
|
JavaScript
DOM 节点列表长度(Node List Length)
DOM 节点列表长度(Node List Length)
|
22天前
|
JavaScript
HTML DOM 节点树
HTML DOM 节点是指在 HTML 文档对象模型中,文档中的所有内容都被视为节点。整个文档是一个文档节点,每个 HTML 元素是元素节点,元素内的文本是文本节点,属性是属性节点,注释是注释节点。DOM 将文档表示为节点树,节点之间有父子和同胞关系。
|
22天前
|
JavaScript
HTML DOM 节点
HTML DOM(文档对象模型)将HTML文档视为节点树,其中每个部分都是节点:文档本身是文档节点,HTML元素是元素节点,元素内的文本是文本节点,属性是属性节点,注释是注释节点。节点间存在父子及同胞关系,形成层次结构。
|
1月前
|
XML JavaScript 数据格式
XML DOM 遍历节点树
XML DOM 遍历节点树