React学习-虚拟DOM

61 阅读6分钟

虚拟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?

  1. 性能优化

    真实 DOM 操作的代价高:

    • 浏览器的 DOM 操作涉及布局(Layout)、绘制(Paint)、合成(Composite)等多个昂贵步骤。
    • 频繁操作会导致页面卡顿。

    在 JS 层面做 diff,避免不必要的 DOM 操作

  2. 跨平台能力:虚拟 DOM 是平台无关的,可用于 Web、React Native(映射到原生组件)等

  3. 声明式编程:开发者只需描述“UI 应该是什么样子”,无需手动管理 DOM 更新逻辑

四、虚拟 DOM 的局限性

  • 内存开销:需要维护两棵 DOM 树(新 + 旧),在大型应用中可能占用较多内存。
  • 并非总是最快:对于简单、静态的 UI,直接操作 DOM 可能更快(如 jQuery)。

⚠️ 提示:合理使用 key(尤其是列表渲染)可极大提升 diff 效率。

React 16+ 的 Fiber 架构:全新工作流

Fiber 引入后,整个协调过程被重构为:

  1. JSX → React 元素(输入不变)

  2. 但协调过程不再直接操作 React 元素树,而是:

    • 构建/更新 Fiber 节点树(内部工作单元)
    • 使用 可中断的循环(而非递归)  遍历 Fiber 树
    • 支持 时间切片(Time Slicing) :每执行一小段工作就检查是否需要让出主线程
    • 基于 优先级调度(Scheduler) 决定哪些更新先执行
  3. 最终在 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 树。
关键步骤:
  1. 从根开始遍历 JSX 生成的 React 元素树

  2. 为每个元素创建或复用 Fiber 节点(通过 beginWork)。

  3. 执行 diff(协调)

    • 比较 typekey
    • 若相同,复用现有 Fiber(避免重建 DOM)。
    • 若不同,标记为删除或替换。
  4. 完成子树后,执行 completeWork

    • 创建/更新 DOM 节点(但不挂载)。
    • 收集副作用(如需要插入、更新、删除的节点)。
  5. 整个 WIP 树构建完成后,进入 Commit 阶段。

🔄 此阶段运行在 render phase,可能被高优先级任务(如用户输入)打断并丢弃。


阶段 2:Commit(提交阶段)

  • 目标:将变更应用到真实 DOM。

  • 不可中断(必须一次性完成,否则 UI 不一致)。

  • 分为三个子阶段:

    1. before mutation:调用 getSnapshotBeforeUpdate 等。
    2. mutation:执行 DOM 操作(插入、更新、删除)。
    3. layout:调用 useLayoutEffectcomponentDidMount/Update

✅ 此阶段操作的是 真实 DOM,并触发生命周期和副作用。

二、那“虚拟 DOM”还存在吗?

存在,但角色变了

  • React 元素(即 JSX 编译结果)仍然是“虚拟 DOM”的表现形式,作为输入描述

  • 协调和 diff 不再直接在 React 元素上进行,而是在 Fiber 节点 上进行。

  • Fiber 节点可以看作是 增强版、可工作的虚拟 DOM,它:

    • 持有 React 元素的信息(type, props
    • 还包含状态(memoizedState)、副作用(effectTag)、父子指针等
    • 支持复用、中断、优先级等

📌 所以: “虚拟 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 流畅。