React 虚拟 DOM
虚拟 DOM 的本质、diff 算法、reconciliation 机制与 React Fiber 架构。
#tech / dev / frame
#resource / react
#type / concept
#status / growing
[!info] related notes
- 所属 MOC: React MOC
- 上位主题: react
- 相关概念: react-hooks, react-performance-overview, [[dom]]
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):
- 同层比较:只比较同一层级的节点,不跨层移动
- 类型判断:元素类型不同,直接销毁重建,不深入比较
- 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 的完整过程:
- 状态变化,触发重新渲染
- 生成新的 VDOM 树
- 与旧 VDOM 树进行 diff
- 计算出需要变更的最小操作集
- 将操作批量应用到真实 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:新的 propsmemoizedState:当前的 state
优先级调度
Fiber 支持优先级:
- 同步优先级:用户输入响应
- 过渡优先级:
useTransition标记的更新 - 低优先级:数据获取完成后的更新
边界与常见误区
- 虚拟 DOM 不一定比直接操作 DOM 快,它的优势在于”足够快 + 声明式 + 可维护”
- 不要滥用
key={index},在列表有增删排序时会导致 bug - React 不保证
setState立即生效,因为 Fiber 可能会延迟处理低优先级更新 - Strict Mode 下组件可能渲染两次,这是为了帮助发现副作用问题
一句话记忆法
虚拟 DOM 是真实 DOM 的 JS 描述,React 用 diff 找出最小变更再批量更新;Fiber 让这个过程可以中断,避免阻塞用户交互。
Related notes
- React MOC
- react-hooks
- [[dom]]