Yasin

Yasin

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 发现:

  • applebanana 只是移动了
  • 只需新增 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} />);

indexkey,头部插入/删除时,每个节点的 key 都变了,和没有 key 效果一样。


Diff 完整流程

状态/props 变化
生成新的虚拟 DOM 树
与旧虚拟 DOM 树做 Diff
生成"变更列表"(patch)
批量更新真实 DOM

React 18 Diff 的变化

React 18 引入并发渲染

  • Diff 过程可以中断和恢复
  • 高优先级更新(用户输入)可以打断低优先级更新(数据渲染)
  • 不再是一次性同步完成整棵树的 Diff

总结

策略 规则
树级别 只比同层,不跨层
组件级别 类型不同直接替换
列表级别 key 识别节点身份