React diff算法
React Diff 算法
是什么
Diff 算法是 React 在状态更新时,比较新旧虚拟 DOM 树的差异,找出最小更新范围,只更新真正变化的部分,而不是全量重建 DOM。
为什么需要 Diff
直接操作真实 DOM 很慢。
React 先在内存里用 虚拟 DOM(VNode) 描述 UI,状态变化时比较新旧 VNode 差异,再把最小变更应用到真实 DOM。
React Diff 的三个核心策略
1. 树级别:只比较同层节点
不跨层级比较:
旧树: 新树:
A A
├── B └── C
└── C └── B
React 不会发现 B 只是"移动了",而是直接:
- 删除旧的 B、C
- 创建新的 C、B
跨层级移动节点代价很高,尽量避免。
2. 组件级别:类型相同才复用
- 类型相同:复用组件实例,继续比较内部
- 类型不同:直接销毁旧组件,创建新组件
// 旧
<Counter />
// 新(类型变了)
<Timer />
// Counter 会被销毁,Timer 重新创建
// 旧
<div className="a" />
// 新(类型相同,只更新属性)
<div className="b" />
// 只更新 className,不重建节点
3. 列表级别:用 key 识别节点身份
没有 key 时,React 按位置对比,容易出错:
// 旧列表
<li>Apple</li> // 位置 0
<li>Banana</li> // 位置 1
// 新列表(头部插入)
<li>Orange</li> // 位置 0
<li>Apple</li> // 位置 1
<li>Banana</li> // 位置 2
React 按位置比:
- 位置 0:
Apple → Orange(更新) - 位置 1:
Banana → Apple(更新) - 位置 2:新增
Banana
3 次更新,实际只需要插入 1 个节点。
有 key 时,React 通过 key 识别节点身份:
<li key="apple">Apple</li>
<li key="banana">Banana</li>
// 新列表
<li key="orange">Orange</li>
<li key="apple">Apple</li>
<li key="banana">Banana</li>
React 发现:
apple和banana只是移动了- 只需新增
orange
大幅减少 DOM 操作。
key 的注意事项
不要用 index 做 key
// ❌ 用 index 做 key,列表顺序变化时 diff 失效
items.map((item, index) => <Item key={index} data={item} />);
// ✅ 用唯一稳定 id
items.map((item) => <Item key={item.id} data={item} />);
用 index 做 key,头部插入/删除时,每个节点的 key 都变了,和没有 key 效果一样。
Diff 完整流程
状态/props 变化
↓
生成新的虚拟 DOM 树
↓
与旧虚拟 DOM 树做 Diff
↓
生成"变更列表"(patch)
↓
批量更新真实 DOM
React 18 Diff 的变化
React 18 引入并发渲染:
- Diff 过程可以中断和恢复
- 高优先级更新(用户输入)可以打断低优先级更新(数据渲染)
- 不再是一次性同步完成整棵树的 Diff
总结
| 策略 | 规则 |
|---|---|
| 树级别 | 只比同层,不跨层 |
| 组件级别 | 类型不同直接替换 |
| 列表级别 | 用 key 识别节点身份 |
