跳到主要内容

diff

React 中的 diff 算法 是虚拟 DOM (Virtual DOM) 更新过程中的核心优化机制,用于高效比较新旧虚拟 DOM 树的差异,并仅将必要的变化同步到真实的 DOM 中。解决了传统虚拟 DOM 全量比较的性能瓶颈,是 React 高效渲染的关键。

这个算法并不是传统意义上的“完整树 diff”,而是一种 启发式优化过的增量对比算法 ,目的是以尽可能低的时间复杂度找出需要更新的部分,从而高效地更新真实 DOM。

一、diff 算法的基本概念

在传统前端渲染中,直接操纵真实 DOM 成本很高(涉及浏览器重排/重绘)。因此 React 提出 虚拟 DOM : 用 JS 对象模拟真实 DOM 结构,当状态变化时,生成新的虚拟 DOM 树,与旧的树对比 ( diff ),找出差异后仅更新真实 DOM 中变化的部分。

备注

在计算机科学中, Diff(Difference)算法 通常指用于比较两个数据结构(如文本、树等)之间差异的算法。例如 Git 使用 diff 来比较代码变更。

但直接比较两棵树的差异复杂度极高(传统树对比是 O(n³) 时间复杂度)。React 基于 启发式规则 对 diff 过程做了大量优化,将复杂度降低到 O(n) ( n 为节点数量)。

二、 React Diff 算法的核心规则

React 的 diff 算法通过以下三个策略大幅降低计算量:

1. 只对比同级节点(Tree Diff)

React 假设 不同层级的节点不会跨层级移动 。因此,diff 只会在同一层级节点间进行,不会跨层级对比。

  • 若某个节点在新旧树中层级不同(如从 <div> 下移动到 <span> 下),React 会直接销毁旧节点及其所有子节点,然后在目标位置重新创建新节点
  • 这一策略将树对比简化为多层级独立的树对比,复杂度从 O(n³) 降至 O(n²)(n 为每层节点数)

2.通过 key标识稳定节点(Element Diff)

对于同一层级的多个子节点(如列表),React 要求为每个节点添加唯一 key,用于标识节点的 稳定性 (即节点在多次渲染中的身份是否不变)。

  • 若没有 key ,React 会默认按顺序对比,当列表顺序变化时(如插入/删除中间项),可能导致大量节点被错误销毁或更新
  • key 时,React 通过 key 匹配新旧节点:
    • 匹配成功:复用节点,仅更新变化的属性
    • 匹配失败:销毁旧节点,创建新节点
    • 未被匹配的新节点:创建
    • 未被匹配的旧节点:销毁

这一策略将子节点对比的复杂度从 O(n²) 降至 O(n)(依赖 key 的高效查找)

3. 组件类型决定复用性(Component Diff)

对于自定义组件,React 会比较组件类型:

  • 若类型不同(如 <div><p>ComponentAComponentB):直接销毁旧组件,创建新组件
  • 若类型相同:复用组件实例,仅更新 props ,触发组件的 shouldComponentUpdateReact.memo 优化逻辑,避免不必要的渲染
方法时间复杂度说明
传统树O(n³)需要尝试所有可能的节点映射,成本极高
React 启发式 diffO(n)基于上面的三个假设,线性遍历即可

三、Fiber 架构下 diff 算法的升级

React 16 引入 Fiber 架构后,diff 算法与 协调过程(Reconciliation) 深度结合,进一步优化了性能和灵活性。

1. 可中断的增量 diff

Fiber 将整个 diff 过程拆分为多个 小任务单元 (Fiber 节点),每个任务执行完一小段时间后会暂停,将控制权交还给浏览器(通过 requestIdleCallback或 requestAnimationFrame),避免长时间阻塞主线程。

  • diff 不再是 “一次性全量计算”,而是 按需分片执行 ,提升了应用的响应速度(尤其是长列表或复杂组件树)

2. 双缓存树(Double Buffering)

Fiber 维护两棵虚拟 DOM 树:current(当前屏幕显示的树)和 workInProgress(正在计算的下一帧树)。

  • diff 过程基于 workInProgress 树进行,完成后直接替换 current
  • 双缓存避免了重复创建树的开销,且 workInProgress 树的节点可直接复用 current 树的节点(通过 alternate 属性),减少内存分配和 GC 压力

3. 更细粒度的节点标记

Fiber 为每个节点标记 副作用( Effect ,如 Placement(插入)、 Update(更新)、 Deletion (删除)。

  • diff 完成后,React 仅需遍历带有副作用的节点,批量更新真实 DOM,进一步减少操作次数
信息

Fiber 并没有改变 diff 算法的核心逻辑,只是让 diff 过程变得更可控、更响应

四、React diff 算法的优势

  • 性能高效 :通过同级对比、key标识和组件类型判断,将树对比复杂度从 O(n³) 降至 O(n) ,大幅提升渲染效率
  • 可中断与并发 :Fiber 架构下的分片 diff 支持任务中断和恢复,避免阻塞主线程,提升应用流畅度(尤其是复杂场景)
  • 灵活的更新控制 :通过 key 、组件类型和副作用标记,精准定位变更,减少不必要的 DOM 操作
  • 内存优化 :双缓存树减少了内存分配和垃圾回收的开销,长期运行更稳定
  • 支持组件级复用 : 对于自定义组件, Diff 会对比组件的 typekey ,若相同

五、避坑

  • 不要使用索引作为列表的 Key :列表增删、排序时,索引会变化,导致 React 误判 “节点已替换”,触发不必要的 DOM 重建(比如列表删除第一个元素,后面的元素 key 从 1→0,React 会认为是 “新节点”)
  • key 必须为一切稳定 : 同一层级的节点 key 不能重复,且不要用随机值(每次渲染生成新 key,会导致节点无法复用,全量重建)
  • 跨层级移动节点要慎重 : Diff 不跨层对比,若频繁将节点从 A 层级移到 B 层级,会导致旧节点销毁、新节点创建,性能较差(尽量通过 CSS 或组件结构优化避免跨层级移动)