Skip to content
目录

Reconciliation 算法(Diff)

Reconciliation(协调)是 React 对比上一棵 Fiber 树本次 render 产出,决定哪些 DOM 需要插入、更新或删除的过程。实现主要在 ReactChildFiber.jsreconcileChildrenArray 等)。


1. 要解决什么

目标做法
少碰 DOM只在 Commit 的 Mutation 阶段批量改 DOM
用启发式 Diff,避免 O(n³) 树编辑距离
可中断Diff 在 Render 阶段完成,结果挂在 Fiber.flags 上

Virtual DOM 不是目的,可预测的更新路径才是。


2. 两条假设(算法前提)

  1. 不同类型 → 不同子树:根类型变了(如 divspan),直接拆掉旧子树、建新子树,不再深入对比。
  2. 同层、同类型用 key 认人:列表重排时靠 key 复用实例,而不是按索引硬比。

因此:不要跨层级移动节点(极少 UI 会这样),列表务必 稳定 key


3. 同层比较在比什么

对同一父节点下的一串子 Fiber,大致维护 lastPlacedIndex(已处理子节点在旧列表中的最右位置):

情况行为
key 相同、类型相同Update,复用 DOM 节点,改 props
key 不同或新节点Placement,插入新 DOM
旧列表有、新列表没有Deletion,删除 DOM

移动:新节点在旧列表中的 index 小于 lastPlacedIndex → 需要移动 DOM(标记 Placement)。

源码用 flagsPlacement | Update | ChildDeletion(见 02-Fiber §5)。


4. key 实践

业务 id数组 index(可重排列表)
稳定、唯一Math.random() 作 key

index 作 key 会导致错误复用、输入框状态错位、动画异常。


5. 与 bailout 的关系

若子树 props/state 未变,Reconciler 可 bailout(跳过子节点 Diff)。这与 Diff 本身正交:Diff 解决「要比」,bailout 解决「要不要比」。


6. 流程简图

mermaid
flowchart TD
  A[render 产出新 React Element] --> B[reconcileChildren]
  B --> C{同层逐项}
  C -->|同 key 同 type| D[Update Fiber]
  C -->|| E[Placement]
  C -->|| F[Deletion]
  D --> G[flags 挂到 Fiber]
  E --> G
  F --> G
  G --> H[Commit Mutation 写 DOM]

7. 阅读路径

文件内容
ReactChildFiber.js单节点、数组 reconcile
ReactFiberBeginWork.js进入 reconcile 的时机
ReactFiberCommitWork.js根据 flags 操作 DOM

8. 总结

  1. 只比同层,类型不同即换树。
  2. key 决定列表复用与移动成本
  3. 结果落在 Fiber.flags,Commit 再统一改 DOM。

工程上:稳定 key + 避免在 render 里乱改结构,比背源码更重要。更多 FAQ 见 09-常见问题

MIT License.