React 虚拟 DOM

虚拟 DOM 的本质、diff 算法、reconciliation 机制与 React Fiber 架构。

#tech / dev / frame #resource / react #type / concept #status / growing

[!info] related notes

React 虚拟 DOM

定义

虚拟 DOM 是真实 DOM 树的轻量 JavaScript 对象表示。React 用它来描述 UI 应该长什么样,然后通过 diff 算法计算出最小变更,再批量更新真实 DOM。

核心流程:state 变化 -> 生成新 VDOM -> diff 新旧 VDOM -> 计算最小 patch -> 批量更新 DOM

为什么需要虚拟 DOM

直接操作真实 DOM 的代价:

  • DOM 操作触发布局计算(reflow)和重绘(repaint)
  • 频繁的 DOM 操作会导致性能问题
  • 手动追踪哪些 DOM 需要更新容易出错

虚拟 DOM 的价值不是”比直接操作 DOM 快”,而是:

  • 声明式描述 UI,开发者不用手动操作 DOM
  • 提供跨平台能力(React Native、SSR)
  • 批量更新,减少不必要的 DOM 操作

VDOM 的数据结构

// JSX
<div className="card">
  <h1>标题</h1>
  <p>内容</p>
</div>

// 编译后的 VDOM 对象(简化)
{
  type: 'div',
  props: {
    className: 'card',
    children: [
      { type: 'h1', props: { children: '标题' } },
      { type: 'p', props: { children: '内容' } }
    ]
  }
}

本质上就是一棵嵌套的普通对象树。

Diff 算法

传统树 diff 算法复杂度是 O(n^3),React 通过三个假设将其降到 O(n):

  1. 同层比较:只比较同一层级的节点,不跨层移动
  2. 类型判断:元素类型不同,直接销毁重建,不深入比较
  3. key 标识:通过 key 标识同一列表中哪些元素是”同一个”
// key 帮助 React 识别列表项的身份
<ul>
  {items.map(item => (
    <li key={item.id}>{item.text}</li>
  ))}
</ul>

为什么用 index 做 key 有问题

// 不推荐:用 index 做 key
{items.map((item, index) => (
  <li key={index}>{item.text}</li>
))}

当列表发生排序、插入、删除时,index 会变,导致 React 误判元素身份,触发不必要的重新渲染甚至状态错乱。

Reconciliation(协调)

Reconciliation 是 React 决定如何更新 UI 的完整过程:

  1. 状态变化,触发重新渲染
  2. 生成新的 VDOM 树
  3. 与旧 VDOM 树进行 diff
  4. 计算出需要变更的最小操作集
  5. 将操作批量应用到真实 DOM
// React 内部大致流程(概念性伪代码)
function reconcile(oldFiber, newElement) {
  if (oldFiber.type !== newElement.type) {
    // 类型不同,替换整个子树
    return { effectTag: 'REPLACEMENT', newElement }
  }
  if (oldFiber.type === newElement.type) {
    // 类型相同,更新属性
    return { effectTag: 'UPDATE', newProps: newElement.props }
  }
}

React Fiber

React 16 引入 Fiber 架构,解决了旧协调器的核心问题:长时间的同步渲染阻塞主线程。

旧架构的问题

  • 递归遍历整棵 VDOM 树,一旦开始就无法中断
  • 大组件树更新时,主线程被长时间占用,导致输入卡顿

Fiber 的核心思想

将渲染工作拆分为可中断的小单元(fiber 节点),每个单元完成后可以暂停,让浏览器处理高优先级任务(如用户输入),然后再继续。

旧架构:一次性递归完成所有工作
Fiber:拆成小单元,可以暂停和恢复

[任务A] -> [任务B] -> [让出主线程] -> [继续任务C] -> ...

Fiber 节点结构

每个 fiber 节点包含:

  • type:元素类型
  • key:标识
  • child:第一个子节点
  • sibling:下一个兄弟节点
  • return:父节点
  • stateNode:对应的 DOM 节点或组件实例
  • pendingProps:新的 props
  • memoizedState:当前的 state

优先级调度

Fiber 支持优先级:

  • 同步优先级:用户输入响应
  • 过渡优先级useTransition 标记的更新
  • 低优先级:数据获取完成后的更新

边界与常见误区

  • 虚拟 DOM 不一定比直接操作 DOM 快,它的优势在于”足够快 + 声明式 + 可维护”
  • 不要滥用 key={index},在列表有增删排序时会导致 bug
  • React 不保证 setState 立即生效,因为 Fiber 可能会延迟处理低优先级更新
  • Strict Mode 下组件可能渲染两次,这是为了帮助发现副作用问题

一句话记忆法

虚拟 DOM 是真实 DOM 的 JS 描述,React 用 diff 找出最小变更再批量更新;Fiber 让这个过程可以中断,避免阻塞用户交互。

创建于 2026/5/27 更新于 2026/5/27