虚拟DOM
一、什么是虚拟 DOM?
虚拟 DOM 是对浏览器中真实 DOM(Real DOM) 的一种轻量级 JavaScript 对象表示。它本质上是一个纯 JavaScript 对象树,结构与真实 DOM 树一致,但不直接操作浏览器的渲染引擎。
例如,真实 DOM 元素:
<div id="container">
<p>Hello, world!</p>
</div>
对应的虚拟 DOM 可能是:
{
type: 'div',
props: {
id: 'container',
children: [
{
type: 'p',
props: {
children: 'Hello, world!'
}
}
]
}
}
注意:React 内部使用的是 Fiber 节点(自 React 16 起),它是对虚拟 DOM 的进一步抽象,支持增量渲染、优先级调度等高级特性,但开发者通常仍将其统称为“虚拟 DOM”。
JSX 编译为 虚拟DOM
在 React 中,JSX 代码会被编译为对
React.createElement()的函数调用,而React.createElement()的返回值就是 虚拟 DOM(即 React 元素) 。这个过程主要依赖 构建工具(如 Babel、TypeScript)的编译转换。JSX本质上是
React.createElement()函数调用的语法糖JSX 约等于(=)
React.createElement()调用
二、React 16之前,虚拟 DOM 的工作流程
React 使用虚拟 DOM 实现高效的 UI 更新,主要分为三步:
1. Render 阶段:生成新的虚拟 DOM 树
- 当组件状态(state)或属性(props)发生变化时,React 会重新调用组件的
render()方法(函数组件即重新执行函数)。 - 生成一棵全新的虚拟 DOM 树(称为“新 VDOM”)。
2. Reconciliation(协调)阶段:Diff 算法比对新旧虚拟 DOM
- React 将新 VDOM 与上一次渲染保存的旧 VDOM 进行差异比较(Diffing) 。
- 使用高效的 启发式 Diff 算法(如基于 key 的列表 diff、同层比较等),找出最小变更集。
- 这个过程发生在内存中,速度极快。
3. Commit 阶段:批量更新真实 DOM
- 将计算出的差异(patches)批量应用到真实 DOM 上。
- 避免频繁、零散地操作真实 DOM,从而减少重排(reflow)和重绘(repaint)。
💡 整个过程可概括为:State/Props 变化 → 生成新 VDOM → Diff 新旧 VDOM → 批量更新真实 DOM
三、为什么需要虚拟 DOM?
-
性能优化:
真实 DOM 操作的代价高:
- 浏览器的 DOM 操作涉及布局(Layout)、绘制(Paint)、合成(Composite)等多个昂贵步骤。
- 频繁操作会导致页面卡顿。
在 JS 层面做 diff,避免不必要的 DOM 操作
-
跨平台能力:虚拟 DOM 是平台无关的,可用于 Web、React Native(映射到原生组件)等
-
声明式编程:开发者只需描述“UI 应该是什么样子”,无需手动管理 DOM 更新逻辑
四、虚拟 DOM 的局限性
- 内存开销:需要维护两棵 DOM 树(新 + 旧),在大型应用中可能占用较多内存。
- 并非总是最快:对于简单、静态的 UI,直接操作 DOM 可能更快(如 jQuery)。
⚠️ 提示:合理使用
key(尤其是列表渲染)可极大提升 diff 效率。
React 16+ 的 Fiber 架构:全新工作流
Fiber 引入后,整个协调过程被重构为:
-
JSX → React 元素(输入不变)
-
但协调过程不再直接操作 React 元素树,而是:
- 构建/更新 Fiber 节点树(内部工作单元)
- 使用 可中断的循环(而非递归) 遍历 Fiber 树
- 支持 时间切片(Time Slicing) :每执行一小段工作就检查是否需要让出主线程
- 基于 优先级调度(Scheduler) 决定哪些更新先执行
-
最终在 Commit 阶段 批量更新真实 DOM
关键变化:
| 方面 | React 15(旧) | React 16+(Fiber) |
|---|---|---|
| 协调算法 | 递归 diff 虚拟 DOM 树 | 迭代式处理 Fiber 树 |
| 执行方式 | 同步、不可中断 | 异步、可中断、可恢复 |
| 数据结构 | 简单的 React 元素 | 复杂的 Fiber 节点(带状态、副作用、指针等) |
| 渲染模型 | 立即渲染 | 支持并发渲染(Concurrent Rendering) |
| 性能瓶颈 | 大更新卡死 UI | 可拆分任务,保持响应性 |
✅ Fiber 完全接管了“如何从虚拟描述生成真实 UI”的全过程。
一、Fiber 架构下的工作流程(渲染循环)
Fiber 将渲染过程分为两个主要阶段:
阶段 1:Reconciliation(协调阶段)
-
目标:对比新旧 UI,计算出需要执行的变更。
-
可中断、可恢复、支持优先级。
-
分为两个子树:
- current tree:当前已提交到屏幕的 Fiber 树。
- work-in-progress (WIP) tree:正在构建的新 Fiber 树。
关键步骤:
-
从根开始遍历 JSX 生成的 React 元素树。
-
为每个元素创建或复用 Fiber 节点(通过
beginWork)。 -
执行 diff(协调) :
- 比较
type和key。 - 若相同,复用现有 Fiber(避免重建 DOM)。
- 若不同,标记为删除或替换。
- 比较
-
完成子树后,执行
completeWork:- 创建/更新 DOM 节点(但不挂载)。
- 收集副作用(如需要插入、更新、删除的节点)。
-
整个 WIP 树构建完成后,进入 Commit 阶段。
🔄 此阶段运行在 render phase,可能被高优先级任务(如用户输入)打断并丢弃。
阶段 2:Commit(提交阶段)
-
目标:将变更应用到真实 DOM。
-
不可中断(必须一次性完成,否则 UI 不一致)。
-
分为三个子阶段:
- before mutation:调用
getSnapshotBeforeUpdate等。 - mutation:执行 DOM 操作(插入、更新、删除)。
- layout:调用
useLayoutEffect、componentDidMount/Update。
- before mutation:调用
✅ 此阶段操作的是 真实 DOM,并触发生命周期和副作用。
二、那“虚拟 DOM”还存在吗?
存在,但角色变了:
-
React 元素(即 JSX 编译结果)仍然是“虚拟 DOM”的表现形式,作为输入描述。
-
但 协调和 diff 不再直接在 React 元素上进行,而是在 Fiber 节点 上进行。
-
Fiber 节点可以看作是 增强版、可工作的虚拟 DOM,它:
- 持有 React 元素的信息(
type,props) - 还包含状态(
memoizedState)、副作用(effectTag)、父子指针等 - 支持复用、中断、优先级等
- 持有 React 元素的信息(
📌 所以: “虚拟 DOM”作为概念仍然存在,但其实现和工作机制已被 Fiber 架构全面覆盖和升级。
三、开发者感知的变化
对大多数开发者来说,API 层面几乎没有变化:
function App() {
return <div>Hello</div>; // 写法不变
}
但底层:
- 不再是简单的
React.createElement→ 递归 diff - 而是
jsx()→ 创建 Fiber 节点 → 协调 → 提交
总结:
Fiber 架构完全覆盖并取代了 React 15 中传统的虚拟 DOM 工作流。
- 它不是“在虚拟 DOM 上加了个调度器”,而是用一套全新的、基于 Fiber 节点的协调系统替代了旧的递归 diff 机制。
- “虚拟 DOM”作为UI 描述的抽象依然存在(即 React 元素),但协调、diff、更新的执行载体已变为 Fiber 树。
- 这一变革使得 React 能够支持并发渲染、自动批处理、Suspense、Transition 等现代特性。
简单说:Fiber 是新一代的“虚拟 DOM 引擎” ,它让 React 流畅。