setState → scheduleUpdateOnFiber → render 阶段(包含 beginWork → reconcileChildren ← Diff 发生处 → completeWork) → commit 阶段 → DOM mutation。
第一轮遍历(从左到右快速匹配,复杂度 O(n)): 同时遍历新旧节点数组,尽可能复用 key 和 type 都相同的前缀节点。如果遇到 key 相同但 type 不同的情况,React 会直接丢弃旧节点并基于新的 React Element 生成全新 Fiber(旧节点放入 deletions 数组待删除),但遍历并不会终止------这说明 React 对待"key 相同、类型变了"的策略是继续往后尝试匹配。如果遇到 key 和 type 都不相同的情况,则立即结束遍历。
第二轮遍历(处理边界):
- 情况一 :旧节点有剩余,新节点已遍历完 → 将剩余旧节点标记为
Deletion; - 情况二:新节点有剩余,旧节点已遍历完 → 为剩余新节点创建新的 Fiber;
第三轮遍历(处理剩余节点): 如果第二轮结束后还有新旧节点未被处理,进入第三轮:
两者都有剩余 → 将剩余旧节点按 key 放入 Map,然后遍历剩余新节点,从 Map 中寻找可复用的节点。在这轮遍历中,React 通过维护 lastPlacedIndex(上一个未移动节点在旧数组中的最大索引)来判断节点是否需要移动。
示例
js
import { useState } from 'react';
const RenderArray = () => {
const [array, setArray] = useState<string[]>(['A','B','C','D','E']);
return (
<div key="render-array">
<h1>RenderArray</h1>
<button onClick={() => setArray(['A','B','E','D','C','F'])}>Set Array</button>
<ul>
{array.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
export default RenderArray;

reconcileChildrenArray
阶段 1:第一轮遍历(同步遍历新旧节点,快速复用同位置节点)

1、A节点
updateSlot 复用节点

updateElement


placeChild。负责确定新 Fiber 节点在 Fiber 树中的位置,并根据情况标记该节点是需要移动、插入还是保持原位,同时返回更新后的 lastPlacedIndex 值。
A 节点,不需要移动。

2、B节点 与A 节点一样的逻辑
3、新节点 E 、旧节点 C,两者 不同

退出当前第一阶段

阶段 2:处理边界情况(旧节点耗尽 或 新节点遍历完)
情况 2.1:新节点遍历完(newIdx === newChildren.length)
情况 2.2:旧节点遍历完(oldFiber === null)
当前情节跳过,进入第三阶段。
阶段 3:第三轮遍历(键映射表复用 + 计算节点移动)
步骤1:构建剩余旧节点的键映射表(key → Fiber)

步骤2:遍历剩余的新节点,从映射表中查找可复用节点
1、E节点, 找到复用。


不移动

2、找到 D 节点,复用,需要移动


3、找到 C节点,复用,需要移动

4、F节点,新节点,标记插入

创建新节点 createFiberFromElement

标记插入

步骤3:标记删除映射表中剩余的旧节点(无新节点匹配)