文章

单节点Diff算法深度解析

单节点Diff算法深度解析

一句话概括

单节点 Diff 的规则很简单:先比 key(是不是同一个身份),再比 type(能不能复用 DOM)——两者都匹配就复用只改属性,有一项不匹配就销毁重建。最多一次 DOM 操作。

核心知识点

1. 判断流程:三次决策

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ReactFiberChild.js 源码简化
function reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes) {
  const key = newChild.key; // 没指定 key 时默认 null
  let child = currentFirstChild;

  while (child) {
    // 决策①:key 是否相同?
    if (child.key !== key) {
      deleteChild(returnFiber, child); // 不同 → 删掉当前旧节点
      child = child.sibling;           // → 继续找下一个兄弟
      continue;
    }

    // 决策②:type 是否相同?
    if (child.elementType === newChild.type) {
      // ✅ key 相同 + type 相同 → 复用!
      deleteRemainingChildren(returnFiber, child.sibling); // 清兄弟
      return useFiber(child, newChild.props); // 复用 DOM,更新 props
    }

    // ❌ key 相同但 type 不同 → 不能复用,全部删除重建
    deleteRemainingChildren(returnFiber, child);
    break;
  }

  // 决策③:没找到可复用的 → 从头创建
  return createFiberFromElement(newChild, returnFiber.mode, lanes);
}

核心公式:key 决定”是不是它”,type 决定”能不能用它的骨架”。

2. 四种实际场景

场景keytype结果DOM 操作
<div /><div />null=null ✅div=div ✅复用0(仅更新属性)
<div /><span />null=null ✅div≠span ❌重建销毁+创建
<div key="a"/><div key="b"/>a≠b ❌重建销毁+创建
<Comp1/><Comp2/>null=null ✅函数不同 ❌重建销毁+创建

3. 为什么 type 不同就必须重建

你无法把一个 <div> DOM 元素变成 <span>——这是浏览器本身的限制。React 不给 DOM 类型做”变形”,直接重建是最正确的选择。

4. deleteRemainingChildren:安全网的清理

即使当前是单节点场景,旧 Fiber 树上可能有多余的兄弟(从多节点变成单节点时):

1
2
3
4
5
6
7
// 之前有 3 个子节点
<div><A/><B/><C/></div>
// 之后变成 1 个
<div><A/></div>

// React 复用 A 后,必须删除 B 和 C
deleteRemainingChildren(returnFiber, child.sibling);

不删的话 B 和 C 的 DOM 会残留在页面上。

5. 无 key 时的行为

没有显式指定 key 时,React 默认为 null。两个无 key 的节点比较时 null === null 总是成立——直接跳过 key 比较阶段。这意味着单节点中不写 key 是安全的,因为没有”和列表里的其他元素对比”的问题。

「其实你每天都在用」

  1. 条件渲染 {isLoggedIn ? <Dashboard /> : <Login />}:切换时 type 不同→全量重建。这就是为什么两个组件各自的状态不互通——因为它们在 Fiber 树上根本不是同一个节点。

  2. {show && <Modal />}:show 从 false 变 true 时,currentFiber 为 null→全新创建。从 true 变 false 时,旧 Fiber 被标记 Deletion→DOM 销毁。Modal 内部的 state 在关闭时全部丢失。

  3. props 更新不重建<UserCard id={newId} />,key 相同(默认 null),type 相同(UserCard 函数),走复用路径。组件本身不卸载,只触发 re-render。

  4. 动画库的”key 重置”技巧:给元素换 key 强制重建 DOM,让 CSS 入场动画重新播放。

  5. React DevTools 里标记的组件:每次切换条件渲染时,你会看到旧组件的 Fiber 被带删除线标记,新组件的 Fiber 在树中出现。

常见误解 (FAQ)

❌ 误区 1:”单节点不需要 key”

技术上不需要。但如果你在两个同 type 的组件之间切换并想强制重建(如清空内部状态),可以手动给不同 key:{mode === 'edit' ? <Form key="edit" /> : <Form key="view" />}——这样 type 相同但 key 不同,React 会销毁+重建。

❌ 误区 2:”条件渲染不写 key 就是安全的”

绝大多数情况是的。但如果你在条件渲染中写 {cond ? <A key="1"/> : <B key="2"/>},两个分支 key 不同,每次切换都是销毁重建——即使 type 相同。除非你需要刻意清空状态,否则别给单节点条件渲染加不同的 key。

❌ 误区 3:”单节点 Diff 就是只比一个”

会比完第一个再沿 sibling 找。如果第一个节点 key 不匹配,React 会继续检查兄弟节点——这在”从多节点变为单节点”的过渡中很重要。

一句话总结

单节点 Diff 的核心原则:先认人(key),再看能不能留(type)——认错人就删,类型不合就重建,全对上就复用——干净利落,一刀切。

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量