在 beginWork 调和阶段,接收父 Fiber、旧子 Fiber 链表、新 JSX children、渲染优先级 lanes ,完成新旧子节点 Fiber 树 Diff,输出全新 workInProgress 子单向链表头节点,实现三大核心目标:
- 尽可能复用旧
Fiber实例,减少新建 / 销毁 - 标记
DOM变更副作用(新增Placement、删除ChildDeletion、移动) - 构建符合新
JSX结构的子Fiber链表,挂载到父Fiber的child字段
javascript
export function reconcileChildren(
current,
workInProgress,
nextChildren,
renderLanes,
) {
if (current === null) {
// ── mount 路径 ──
workInProgress.child = mountChildFibers(
workInProgress,
null, // currentFirstChild = null(首次渲染无旧子节点)
nextChildren,
renderLanes,
);
} else {
// ── update 路径 ──
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child, // 旧子 fiber 链表的头
nextChildren,
renderLanes,
);
}
}
export const reconcileChildFibers = createChildReconciler(true);
export const mountChildFibers = createChildReconciler(false);
两个路径使用同一组内部函数 createChildReconciler,负责 Diff 算法中的子节点比较
| 参数 | mountChildFibers |
reconcileChildFibers |
|---|---|---|
shouldTrackSideEffects |
false | true |
currentFirstChild |
null(mount 无旧节点) |
current.child(有旧节点) |
| 适用场景 | 首次渲染(root.render) |
后续更新(setState) |
| 副作用行为 | 不记录 Placement/Deletion(所有节点都是新的) |
记录 Placement/Deletion(需要给 commit 阶段指出的 DOM 操作) |
一、完整调用链路
typescript
updateFunctionComponent/updateHostComponent/... (beginWork tag 分发尾)
│
└─ reconcileChildren(current, workInProgress, nextChildren, renderLanes)
│
├─ current === null?
│ ├─ Yes: mountChildFibers(shouldTrackSideEffects=false)
│ └─ No: reconcileChildFibers(shouldTrackSideEffects=true)
│
└─ reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes)
│
├─ 顶层 Fragment 展平
│
├─ typeof === 'object'?
│ ├─ REACT_ELEMENT_TYPE → reconcileSingleElement
│ │ └─ deleteChild ◄── deleteRemainingChildren
│ │ └─ 遍历旧 fiber,key 匹配 → useFiber(复用)
│ │ └─ 创建新 fiber → createFiberFromElement/createFiberFromFragment
│ │
│ ├─ REACT_PORTAL_TYPE
│ │ └─ reconcileSinglePortal(同上模式)
│ │
│ ├─ REACT_LAZY_TYPE
│ │ └─ resolveLazy + 递归
│ │
│ ├─ Array → reconcileChildrenArray
│ │ ├─ ① 顺序匹配 (updateSlot) ◄── updateTextNode / updateElement / updatePortal / updateFragment
│ │ ├─ ② 新数组完?→ deleteRemainingChildren
│ │ ├─ ③ 旧节点完?→ createChild 剩余全部新建
│ │ └─ ④ Map + 遍历
│ │ ├─ updateFromMap → 复用/创建 ◄── updateTextNode / updateElement / updatePortal / updateFragment
│ │ ├─ placeChild → 设 Placement/lastPlacedIndex
│ │ └─ 未匹配旧节点 → deleteChild
│ │
│ ├─ Iterable
│ │ └─ reconcileChildrenIteratable
│ │
│ ├─ AsyncIterable
│ │ └─ reconcileChildrenAsyncIteratable
│ │
│ ├─ Promise
│ │ ├─ unwrapThenable
│ │ └─ 递归 reconcileChildFibersImpl
│ └─ Context
│ ├─ readContextDuringReconciliation
│ └─ 递归 reconcileChildFibersImpl
│
├─ string/number/bigint
│ └─ reconcileSingleTextNode + placeSingleChild
│
└─ null/undefined/boolean
└─ deleteRemainingChildren
│
▼
workInProgress.child = 结果 fiber 链表头
二、createChildReconciler 源码解析
总体架构采用工厂模式,根据入参 shouldTrackSideEffects 创建一套子节点调和工具集 ,返回内部所有子调和函数组成的闭包对象 ChildReconciler,分为以下几类工具:
- 删除相关:
deleteChild/deleteRemainingChildren - 旧子节点索引映射:
mapRemainingChildren Fiber复用 / 克隆:useFiber- 位置摆放(移动 / 插入标记):
placeChild/placeSingleChild - 各类节点更新构造:
updateTextNode/updateElement/updatePortal/updateFragment - 全新节点创建:
createChild - 单
key快速匹配(快路径):updateSlot Map查表批量匹配(慢路径):updateFromMap- 单元素子节点调和:
reconcileSingleElement/reconcileSinglePortal/reconcileSingleTextNode - 数组类元素子节点调和:
reconcileChildrenArray/reconcileChildrenIterable/ /reconcileChildrenAsyncIteratable/reconcileChildrenIterator - 入口函数:
reconcileChildFibersImpl/reconcileChildFibers
分类一、删除子节点
deleteChild
javascript
function deleteChild(returnFiber, childToDelete) {
// 注水 / 纯 bailout 场景无需记录删除,无 DOM 操作
if (!shouldTrackSideEffects) return;
// 读取父 Fiber 的 deletions 数组:存储待删除子 Fiber
const deletions = returnFiber.deletions;
if (deletions === null) {
// 数组为空则初始化数组,并给父节点打上 ChildDeletion 副作用标记
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
// 已有数组直接 push 待删除子节点
deletions.push(childToDelete);
}
}
作用
将 childToDelete fiber 加入父 fiber(returnFiber)的 deletions 数组,同时在父 fiber 上设置 ChildDeletion flag
设计意义:
- 副作用收集延迟执行 :不在
reconcile阶段立即删除fiber或DOM,仅记录,统一聚合到父节点。commit阶段遍历deletions数组批量执行DOM移除和fiber解引用
deleteRemainingChildren
javascript
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if (!shouldTrackSideEffects) return null;
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null; // ← 总是返回 null
}
作用
遍历旧子节点链表,全部调用 deleteChild 加入删除列表;用于新子节点长度小于旧子节点,末尾全部清除场景
设计意义:
- 批量操作 :在
reconcileSingleElement中匹配到目标节点后,对剩余兄弟节点调用此函数------"找到了,后面的不要了" - 返回值固定为
null:调用者的返回值直接作为reconcileChildFibersImpl的返回值,表达"本次reconciliation没有产出新fiber"
分类二、工具函数组(Utility)
mapRemainingChildren旧子节点 key 映射表
javascript
function mapRemainingChildren(currentFirstChild) {
const existingChildren = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key === null) {
existingChildren.set(existingChild.index, existingChild);
} else if (enableOptimisticKey && existingChild.key === REACT_OPTIMISTIC_KEY) {
existingChildren.set(-existingChild.index - 1, existingChild);
} else {
existingChildren.set(existingChild.key, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
作用
将剩余旧 fiber 链表构建为 Map<key | index, Fiber>,分三类存储
- 节点无 key:用数组下标
index作为 Map 键 - 乐观 key(
REACT_OPTIMISTIC_KEY):存负下标-index-1,区分普通下标 - 普通字符串 / 数字 key:直接使用 key 做键
设计意义:
- O(1) 查找 :数组
diff第二阶段的核心数据结构。当顺序匹配中断后,React 不遍历旧链表中剩余节点与新节点对比(O(n²)),而是将剩余旧节点构建为Map<key, Fiber>。后续每个新节点通过key或index从Map中 O(1) 查找可复用fiber,将链表 O (n) 查找转为 Map O (1) 查表,解决子节点乱序重排场景,避免 O(n²) 双重循环,这是React list diff的核心优化。 - key vs index 双索引 :有
key的节点用key做键,无key的节点用index做键------保证无key节点也能通过位置匹配
useFiber复用旧 Fiber
javascript
function useFiber(fiber, pendingProps) {
// 基于旧 current Fiber 创建 workInProgress 克隆(双缓冲核心 API)
const clone = createWorkInProgress(fiber, pendingProps);
// 避免旧下标、旧兄弟指针干扰新一轮子节点链表构建
clone.index = 0;
clone.sibling = null;
// 返回可复用新 Fiber
return clone;
}
作用
通过 createWorkInProgress 克隆现有 fiber,重置 index 和 sibling。
设计意义:
- 最大化复用现有
fiber实例 :不是销毁再创建,而是复用fiber对象的stateNode(DOM节点/组件实例),只更新pendingProps,减少内存分配 index/sibling重置 :index=0和sibling=null是因为placeChild和reconcileChildrenArray会在后续重新设置这些值------在useFiber中重置,避免旧下标、旧兄弟指针干扰新一轮子节点链表构建
分类三、位置摆放、插入 / 移动标记
placeChild多子列表场景
javascript
function placeChild(newFiber, lastPlacedIndex, newIndex) {
// 赋值新下标,记录新位置
newFiber.index = newIndex;
// 挂载或注水仅打 Forked 分叉标记用于 useId 计算,不标记 DOM 插入
if (!shouldTrackSideEffects) {
newFiber.flags |= Forked; // 仅为水合 id 生成
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
// 存在旧 Fiber(复用节点)
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// 旧下标 < lastPlacedIndex → 该节点需要移动,打上 Placement
newFiber.flags |= Placement | PlacementDEV; // ← 移动
return lastPlacedIndex;
} else {
// 否则位置不变,返回旧下标更新 lastPlacedIndex
return oldIndex; // ← 原位
}
} else {
// 无旧 Fiber(全新节点):标记插入 Placement
newFiber.flags |= Placement | PlacementDEV; // ← 插入
return lastPlacedIndex;
}
}
作用
跟踪"最近一次不需要移动的旧节点的索引"。lastPlacedIndex 记录上一个已经摆放完成的最大旧下标;当新 fiber 的 oldIndex >= lastPlacedIndex 时,说明此节点在旧列表中的位置"足够靠后",不需要移动。当 oldIndex < lastPlacedIndex 时,此节点需要被移动到当前 newIndex 的位置
设计意义
最少移动量算法 :在从左到右扫描新列表时,如果旧节点的索引不断增大,那这些节点在新列表中的相对顺序与旧列表一致,无需移动。只有当旧索引回退时(oldIndex < lastPlacedIndex),才需要移动
placeSingleChild单个子节点
javascript
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement | PlacementDEV;
}
return newFiber;
}
作用
单节点场景无需比较下标,仅区分全新节点标记插入,复用节点不标记
分类四、各类节点更新复用
统一逻辑:存在同类型旧 fiber → useFiber 复用;不存在 → 创建全新 fiber
updateTextNode文本节点
javascript
function updateTextNode(returnFiber, current, textContent, lanes) {
if (current === null || current.tag !== HostText) {
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
const existing = useFiber(current, textContent);
existing.return = returnFiber;
return existing;
}
}
作用
- 无旧节点 / 旧节点非
HostText:调用createFiberFromText创建文本fiber; - 存在匹配旧文本
fiber:useFiber复用,更新文本props; - 绑定
return父指针
设计意义
文本节点 fiber 可复用的条件是旧 fiber 也是 HostText 。如果旧位置是一个 DOM 元素(如 <span>)而现在变成了文本,不能复用------必须创建新 fiber
updateElementdiv / 组件等普通元素
javascript
function updateElement(returnFiber, current, element, lanes) {
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
const update = updateFragment(returnFiber, current, element.props.children, lanes, element.key);
if (enableFragmentRefs) {
coerceRef(updated, element);
}
return update
}
if (current !== null) {
if (
current.elementType === elementType || // ← 类型匹配
(typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === current.type)
) {
const existing = useFiber(current, element.props);
coerceRef(existing, element);
existing.return = returnFiber;
return existing;
}
}
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(created, element);
created.return = returnFiber;
return created;
}
可复用条件
current.elementType === elementType(组件/标签类型完全一致)Lazy组件解析后类型匹配
作用
- 复用:
useFiber; - 不匹配:全新创建
createFiberFromElement - 同步
ref
设计意义
类型不同(如 <div> → <span>)→ 删除旧 DOM 创建新 DOM,即 unmount + remount
updatePortal
javascript
function updatePortal(returnFiber, current, portal, lanes) {
if (
current === null ||
current.tag !== HostPortal ||
current.stateNode.containerInfo !== portal.containerInfo || // ← 容器不同
current.stateNode.implementation !== portal.implementation // ← 实现不同
) {
// 创建新 Portal fiber
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
const existing = useFiber(current, portal.children || []);
existing.return = returnFiber;
return existing;
}
}
设计意义
Portal 的复用除了 tag === HostPortal,还需检查 containerInfo(目标 DOM 容器)和 implementation 是否相同。容器变了必须重建,因为 DOM 节点挂载到不同容器
updateFragment片段
javascript
function updateFragment(returnFiber, current, fragment, lanes, key) {
if (current === null || current.tag !== Fragment) {
const created = createFiberFromFragment(fragment, returnFiber.mode, lanes, key);
created.return = returnFiber;
return created;
} else {
const existing = useFiber(current, fragment);
existing.return = returnFiber;
return existing;
}
}
设计意义
Fragment fiber 不产生真实 DOM,只是包裹子节点。复用条件简单------旧 fiber 也是 Fragment 即可
分类五、全新节点创建入口
javascript
function createChild(returnFiber, newChild, lanes) {
if (typeof newChild === 'string' || typeof newChild === 'number' || typeof newChild === 'bigint') {
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
lanes,
);
created.return = returnFiber;
return created;
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
lanes,
);
coerceRef(created, newChild);
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
lanes,
);
created.return = returnFiber;
return created;
}
case REACT_LAZY_TYPE: {
const resolvedChild = resolveLazy(newChild);
// 递归
const created = createChild(returnFiber, resolvedChild, lanes);
return created;
}
}
if (isArray(newChild) || getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')
) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
lanes,
null,
);
created.return = returnFiber;
return created;
}
if (typeof newChild.then === 'function') {
const thenable = newChild;
// 递归
const created = createChild(
returnFiber,
unwrapThenable(thenable),
lanes,
);
return created;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context = newChild;
// 递归
return createChild(
returnFiber,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
}
return null; // null/undefined/boolean → 无 fiber(空插槽)
}
作用
接收原始 JSX 子值(string/number/ReactElement/Promise/Context/ 数组迭代器等),分发对应构造器,全新创建 fiber(无旧节点可复用场景),分支覆盖所有合法子节点类型:
- 基础文本:
string/number/bigint→createFiberFromText ReactElement→createFiberFromElementPortal→createFiberFromPortalLazy组件:递归解析lazy后重新createChild- 数组 / 迭代器 / 异步迭代器:创建
Fragment包裹 Promise thenable:解包后递归创建Context对象:读取上下文值后递归创建- 非法类型:返回
null
设计意义
createChild 与 updateSlot/updateFromMap 对应------后者先尝试复用,复用时调 update*,无法复用时调 createChild
分类六、快路径单 key 匹配
javascript
function updateSlot(returnFiber, oldFiber, newChild, lanes) {
const key = oldFiber !== null ? oldFiber.key : null;
// 文本/数值 → 旧节点必须有 null key 才能复用
if ((typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint') {
if (key !== null) return null; // key 冲突 → 匹配失败
return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
}
// ReactElement → key 必须匹配
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
if (newChild.key === key) {
return updateElement(
returnFiber,
oldFiber,
newChild,
lanes,
);
} else return null; // key 不匹配 → 匹配失败
case REACT_PORTAL_TYPE:
if (newChild.key === key) return updatePortal(eturnFiber, oldFiber, newChild, lanes);
else return null;
case REACT_LAZY_TYPE:
const resolvedChild = resolveLazy(newChild);
return updateSlot(
returnFiber,
oldFiber,
resolvedChild,
lanes,
);
}
// 数组/迭代器 → key 必须为 null(无 key fragment)
if (isArray(newChild) || getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')) {
if (key !== null) return null;
return updateFragment(
returnFiber,
oldFiber,
newChild,
lanes,
null,
);
}
if (typeof newChild.then === 'function') {
const thenable = newChild;
const updated = updateSlot(
returnFiber,
oldFiber,
unwrapThenable(thenable),
lanes,
);
return updated;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context= newChild;
return updateSlot(
returnFiber,
oldFiber,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
}
return null; // 任何其他情况 → 匹配失败
}
作用
新旧子节点顺序一一对应,顺序相近、少量移动(数组轻微增删)。在reconcileChildrenArray 的第一阶段(顺序匹配)中,检查新子节点是否与当前旧 fiber 的 key 匹配。匹配则复用,不匹配返回 null 中断顺序匹配
- 对比新旧
key,key完全一致才尝试复用;key不一致直接返回null,切换慢路径; - 按子类型分发
updateTextNode/updateElement/updatePortal复用旧fiber; - 处理
Lazy/Promise/Context递归解包
设计意义
顺序扫描的"单步匹配器"。每次处理一对 (oldFiber, newChild[newIdx]),基于 key 判断是否同一位置。返回 null 即表示"位置不匹配,停止顺序扫描,切换到 Map 模式"。顺序列表优先走线性快路径,避免构建 Map 带来的内存开销,性能最优
分类七、慢路径 Map 查表匹配
javascript
function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) {
// 文本 → key = newIdx
if ((typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint') {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) ||
(enableOptimisticKey &&
existingChildren.get(-newIdx - 1)) ||
null;
const updated = updateElement(
returnFiber,
matchedFiber,
newChild,
lanes,
);
return updated;
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) ||
(enableOptimisticKey &&
existingChildren.get(-newIdx - 1)) ||
null;
return updatePortal(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_LAZY_TYPE: {
const resolvedChild = resolveLazy((newChild: any));
const updated = updateFromMap(
existingChildren,
returnFiber,
newIdx,
resolvedChild,
lanes,
);
return updated;
}
}
if (
isArray(newChild) ||
getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')
) {
const matchedFiber = existingChildren.get(newIdx) || null;
const updated = updateFragment(
returnFiber,
matchedFiber,
newChild,
lanes,
null,
);
return updated;
}
if (typeof newChild.then === 'function') {
const thenable = newChild;
const updated = updateFromMap(
existingChildren,
returnFiber,
newIdx,
unwrapThenable(thenable),
lanes,
);
return updated;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context = newChild;
return updateFromMap(
existingChildren,
returnFiber,
newIdx,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
}
return null;
}
作用
在 reconcileChildrenArray 的第二阶段(Map 模式)中,从 existingChildren Map 中按 key 查找可复用的旧 fiber,然后调用对应的 update* 函数
- 根据新子节点类型,用
key/index/ 乐观负下标去旧节点Map查表 - 查到匹配旧
fiber则复用,否则等同于新建 - 兼容文本、
Element、Portal、Lazy、Promise、Context所有子类型
设计意义 顺序匹配断裂后的"回退查找器"。不再假设 oldFiber 与 newChild[newIdx] 位置对应,而是通过 key 在剩余旧节点中精确查找可复用 fiber。快路径匹配失败后降级到 Map 查表,保证任意列表结构都能正确 Diff,兼顾两种极端场景性能
分类八、单元素子节点调和
reconcileSingleElement
当父节点新 children 是单个 ReactElement(<div />/ 组件 / Fragment) 时调用,用于:
- 在旧子
fiber链表中按key寻找可复用旧fiber - 匹配成功:校验类型一致,删除其余兄弟旧节点,复用
fiber key匹配但类型不兼容:删除整条旧子链表,新建fiberkey全部不匹配:逐个删除所有旧子节点,最后创建全新fiber
javascript
function reconcileSingleElement(
returnFiber, // 父 fiber(当前正在调和的父节点)
currentFirstChild, // 旧树第一个子 fiber(current.child)
element, // 本次渲染新的单个 JSX Element 对象
lanes // 本次渲染优先级车道,用于新建 Fiber 标记调度优先级
) {
// 提取新元素唯一匹配 key,作为旧节点查找标准
const key = element.key;
// 遍历指针,从旧链表第一个子节点开始遍历
let child = currentFirstChild;
// 遍历全部旧子 Fiber 链表,匹配 key
while (child !== null) {
if (child.key === key ||
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)) {
// 旧节点 key 和新 element key 完全相等或开启乐观 key 特性,旧节点是乐观占位 key
const elementType = element.type;
// 判断新节点是 <> 片段
if (elementType === REACT_FRAGMENT_TYPE) {
// 校验旧 Fiber 也必须是 Fragment,类型一致才可复用
if (child.tag === Fragment) {
// 当前旧节点匹配可复用,单元素场景只能保留这一个子节点,删除该节点后面所有兄弟旧 Fiber,加入父节点删除列表
deleteRemainingChildren(returnFiber, child.sibling);
// 基于旧 Fragment 克隆 workInProgress,props 替换为新 children
const existing = useFiber(child, element.props.children);
// 把临时乐观 key 覆盖为真实 element.key
if (enableOptimisticKey) {
existing.key = key;
}
// 同步 Fragment 绑定的 ref 到新克隆 Fiber
if (enableFragmentRefs) {
coerceRef(existing, element);
}
// 绑定父子指针,维持 Fiber 树结构
existing.return = returnFiber;
// 直接返回复用后的 Fiber,终止整个调和流程
return existing;
}
} else {
// 新元素是普通组件 / DOM 元素 / Lazy 组件
// 新旧 elementType 完全一致(普通组件、div、span 等)或 新元素是 REACT_LAZY_TYPE 懒加载组件,解析后真实类型与旧 Fiber 匹配
if (
child.elementType === elementType ||
(typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
// 匹配成功 → 删除当前节点之后全部旧兄弟
deleteRemainingChildren(returnFiber, child.sibling);
// 克隆复用旧 Fiber,替换 props
const existing = useFiber(child, element.props);
// 覆盖真实 key、同步 ref、绑定父指针,返回复用 Fiber
if (enableOptimisticKey) {
existing.key = key;
}
coerceRef(existing, element);
existing.return = returnFiber;
return existing;
}
}
// 同 key 不同类型无法复用,整条旧子链表全部作废,后续走新建 fiber 逻辑
// key 匹配但类型不匹配 → 从当前匹配节点开始,删除包括自身在内所有剩余旧子节点
deleteRemainingChildren(returnFiber, child);
// 跳出 while 循环,不再遍历后续旧节点
break;
} else {
// 当前旧节点 key 和新元素不匹配,将单个旧节点加入删除列表
deleteChild(returnFiber, child); // key 不同 → 删除
}
// 游标移动到下一个旧子节点,继续循环匹配
child = child.sibling;
}
// 循环走完,无任何可复用节点 → 创建新 fiber
if (element.type === REACT_FRAGMENT_TYPE) {
// fragment:调用 createFiberFromFragment 创建片段 fiber,绑定 ref、校验 Fragment 合法 props,绑定父 return 指针,返回全新 Fiber
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
if (enableFragmentRefs) {
coerceRef(created, element);
}
created.return = returnFiber;
validateFragmentProps(element, created, returnFiber);
return created;
} else {
// 普通元素:调用 createFiberFromElement 创建标准 Element Fiber,绑定父 return 指针,返回全新 Fiber
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(created, element);
created.return = returnFiber;
return created;
}
}
核心 Diff 规则
- 匹配优先级 :
key> 元素类型,key是首要匹配标识 ,匹配优先级:key> 元素类型, key匹配后必须元素类型一致才可复用- 单新元素场景最终只能保留一个子节点,其余旧兄弟全部标记删除
- 只向父
Fiber收集待删除列表、标记ChildDeletion副作用不操作DOM,真实删除在commit阶段执行
作用
- 外层循环 :遍历旧子
Fiber链表,按key筛选可复用候选; - key 匹配且类型一致 :复用当前
Fiber,删除所有后续旧兄弟,直接返回复用节点; - key 匹配但类型不一致:整条旧链表全部删除,跳出循环准备新建;
- key 完全不匹配:逐个删除旧节点,继续遍历;
- 遍历结束无匹配 :根据元素类型新建
Fragment / Element Fiber并返回
设计意义
链表遍历性能最优策略
- 找到合法复用节点后立刻删除后续全部兄弟,直接
return,提前终止链表循环 key不匹配节点逐个标记删除,不做多余存储- 同
key类型冲突直接清空整条链表,避免无效遍历
reconcileSinglePortal
当父节点新子节点是单个 createPortal 对象时执行本调和逻辑,职责:
- 在旧子
Fiber链表中按key匹配候选HostPortal Fiber - 三重校验:
key、tag、挂载容器、实现对象全部一致才可复用 - 单
Portal场景只能保留一个子节点,其余旧兄弟全部标记删除 - 匹配失败则清空全部旧子节点,全新创建
Portal Fiber
ini
function reconcileSinglePortal(
returnFiber,
currentFirstChild,
portal, // 新渲染的 ReactPortal 对象
lanes,
) {
const key = portal.key;
let child = currentFirstChild;
while (child !== null) {
// 旧节点 key 与新 portal key 完全相等或开启乐观 key 特性,旧节点是临时乐观占位 key
if (
child.key === key ||
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)
) {
// 旧 Fiber 类型必须是 Portal 且 Portal 挂载的目标 DOM 容器不能变且平台渲染器实现实例不变
if (
child.tag === HostPortal &&
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
// 删除当前匹配节点后面所有旧兄弟,加入父节点删除列表
deleteRemainingChildren(returnFiber, child.sibling);
// 双缓冲克隆 workInProgress Fiber,更新 props 为新 portal 的 children
const existing = useFiber(child, portal.children || []);
// 把临时乐观 key 替换为 Portal 真实 key
if (enableOptimisticKey) {
existing.key = key;
}
// 绑定父子指针,构建完整 Fiber 树
existing.return = returnFiber;
// 直接返回复用 Fiber,终止整个调和流程
return existing;
} else {
// key 匹配,但 Portal 三校验不通过(类型 / 容器 / 实现不一致)
// 删除当前节点及之后全部旧子节点,整条旧链表作废
deleteRemainingChildren(returnFiber, child);
// 跳出 while 循环,进入末尾新建逻辑
break;
}
} else {
// 当前旧节点 key 不匹配,单独标记删除
deleteChild(returnFiber, child);
}
// 游标移动到下一个旧子节点继续循环匹配
child = child.sibling;
}
// 循环遍历完毕,无任何可复用节点:全新创建 Portal Fiber
// 根据 portal 对象新建 HostPortal 类型 Fiber,携带渲染模式、调度 lanes
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
// 绑定父节点指针
created.return = returnFiber;
// 返回全新 Portal Fiber
return created;
}
作用
- 提取
Portal唯一匹配key,初始化旧子链表遍历 - 循环遍历旧子节点,筛选
key匹配的候选Portal - 对
key匹配节点做三重强校验:tag、挂载容器、平台实现- 校验全部通过:删除后续所有旧兄弟,克隆复用
Portal Fiber并返回 - 校验不通过:清空整条旧链表,跳出循环
- 校验全部通过:删除后续所有旧兄弟,克隆复用
key不匹配的旧节点逐个标记删除,继续遍历- 遍历结束无可用旧
Portal,全新创建HostPortal Fiber返回
reconcileSingleTextNode
父节点新子节点是纯文本字符串 / 数字 时调用,专门调和单个文本子节点,对应 HostText Fiber。文本节点没有 key,Diff 规则极简:
- 旧第一个子节点恰好是
HostText→ 直接复用,删掉其余所有旧兄弟; - 否则整条旧子链表全部删除,新建文本
Fiber
kotlin
function reconcileSingleTextNode(
returnFiber,
currentFirstChild,
textContent,
lanes,
) {
// 旧首子是文本节点
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// 删除当前文本节点后面所有旧兄弟
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
// 基于旧 current Fiber 创建 workInProgress,把 props 替换为新文本内容
const existing = useFiber(currentFirstChild, textContent);
// 绑定父子指针,维护 Fiber 树层级
existing.return = returnFiber;
// 直接返回复用后的文本 Fiber,调和结束
return existing;
}
// 无旧子节点,或旧首节点不是 HostText
// 包含自身在内全部旧子节点标记删除
deleteRemainingChildren(returnFiber, currentFirstChild);
// 生成 tag=HostText 的全新 Fiber,传入文本内容、渲染模式、优先级 lanes
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
// 绑定父节点
created.return = returnFiber;
// 返回新建文本 Fiber 作为父节点唯一子节点
return created;
}
作用
- 判断旧树第一个子节点是否为
HostText- 是:删除该节点所有后续旧兄弟,克隆复用旧文本
Fiber并返回 - 否:将全部旧子节点标记删除
- 是:删除该节点所有后续旧兄弟,克隆复用旧文本
- 创建全新
HostText Fiber,绑定父指针并返回
分类九、数组元素子节点调和
reconcileChildrenArray
数组列表子节点 Diff 的核心实现 ,当 children 是数组 / 可迭代列表时执行,分为三大阶段:
- 快路径线性遍历 :新旧列表顺序大体一致,
updateSlot按顺序key一一匹配;一旦key不匹配立刻退出快路径 - 两种兜底分支
- 旧节点遍历完毕,只剩新增节点,直接
createChild创建 - 快路径匹配失败,进入慢路径
Map查表mapRemainingChildren + updateFromMap,处理乱序、大量增删场景
- 旧节点遍历完毕,只剩新增节点,直接
- 收尾清理 Map 中未复用的旧节点,标记删除,构建全新
workInProgress子Fiber链表并返回
ini
function reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChildren, // 本次渲染新数组子节点列表
lanes,
) {
// 最终构建完成的新子链表头节点(函数返回值),初始空
let resultingFirstChild = null;
// 新链表尾指针,用于串联 sibling
let previousNewFiber = null;
// 旧链表遍历游标,起点为旧第一个子节点
let oldFiber = currentFirstChild;
// 移动算法标记,记录上一个可复用旧节点最大下标,判断是否需要移动 DOM
let lastPlacedIndex = 0;
// 新数组当前遍历下标
let newIdx = 0;
// 预缓存下一个旧节点,匹配失败时恢复指针
let nextOldFiber = null;
// 顺序线性匹配 for 循环
// 循环终止条件:旧链表遍历完或新数组遍历完
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 旧节点下标大于当前新下标,说明旧节点被提前跳过,中间删除过元素导致旧下标断层
if (oldFiber.index > newIdx) {
// 暂存到 nextOldFiber,置空 oldFiber
nextOldFiber = oldFiber;
oldFiber = null;
} else {
// 缓存 oldFiber.sibling 到 nextOldFiber,保存下一轮旧节点指针
nextOldFiber = oldFiber.sibling;
}
// 按顺序对比新旧节点 key,key 一致则复用以旧 Fiber,返回新 workInProgress Fiber;key 不匹配返回 null
const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
// key 不匹配,快路径失效;
if (oldFiber === null) {
// 恢复旧指针 oldFiber = nextOldFiber
oldFiber = nextOldFiber;
}
// 跳出快路径循环,进入慢路径
break;
}
// 客户端渲染(非注水)才标记删除
if (shouldTrackSideEffects) {
// 代表 key 匹配但类型不同,无法复用
if (oldFiber && newFiber.alternate === null) {
// 将旧节点加入删除列表
deleteChild(returnFiber, oldFiber);
}
}
// 更新 Fiber index,根据 lastPlacedIndex 判断是原地保留 / 移动 / 新增,打上 Placement 副作用,返回新的 lastPlacedIndex
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// 当前是新链表第一个节点,赋值给 resultingFirstChild
resultingFirstChild = newFiber;
} else {
// 把上一个新节点 .sibling 指向当前新 Fiber
previousNewFiber.sibling = newFiber;
}
// 更新尾指针
previousNewFiber = newFiber;
// 移动旧游标到预缓存的 nextOldFiber
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// 新数组全部遍历完成,旧链表还有剩余节点
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
// SSR 注水环境:推入树分叉计数,供 useId 稳定生成 ID
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
// 直接返回构建好的新链表头,函数结束
return resultingFirstChild;
}
if (oldFiber === null) {
// 快路径遍历完所有旧节点,新数组还有未处理元素,全部是新增节点
for (; newIdx < newChildren.length; newIdx++) {
// 逐个根据新子元素创建全新 Fiber
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
// 全部标记插入 Placement
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 串联新链表
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
// 注水环境记录分叉
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
// 返回链表头
return resultingFirstChild;
}
// 快路径匹配失败,进入慢路径 Map 查表
// 慢路径旧节点 key → Fiber 映射 Map
const existingChildren = mapRemainingChildren(oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
// 用当前新下标 /key 去 Map 查找可复用旧 Fiber,匹配则克隆复用,无匹配返回新建 Fiber
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
const currentFiber = newFiber.alternate;
if (currentFiber !== null) {
// 匹配成功后,从 Map 中删除已复用旧节点(避免后续重复复用)
if (enableOptimisticKey && currentFiber.key === REACT_OPTIMISTIC_KEY) {
existingChildren.delete(-newIdx - 1);
} else {
existingChildren.delete(currentFiber.key === null ? newIdx : currentFiber.key);
}
}
}
// 标记插入 / 移动副作用
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 串联新 Fiber 链表
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
// Map 中残留的 Fiber 代表新列表无对应 key,全部标记删除,加入父节点 deletions
if (shouldTrackSideEffects) {
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
// 注水分叉记录
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
// 返回完整构建的新子链表头
return resultingFirstChild;
}
作用
- 初始化新旧链表遍历指针、新链表头尾标记、移动算法基准下标
- 快路径线性顺序遍历新旧列表,
updateSlot按key顺序匹配- 匹配成功:标记移动 / 插入副作用,串联新
Fiber链表 key不匹配:跳出快路径
- 匹配成功:标记移动 / 插入副作用,串联新
- 快速分流三种收尾场景:
- 新数组遍历完毕:删除剩余旧节点,直接返回
- 旧链表遍历完毕:批量新建剩余新节点,返回
- 两者都有剩余:进入慢路径
- 慢路径构建旧节点
key Map,遍历剩余新数组查表复用旧Fiber Map内未匹配旧节点全部标记删除- 注水环境记录树分叉用于
useId,返回新子链表头节点
设计思想
- 快慢双路径自适应
Diff- 稳定有序列表走快路径线性遍历 ,无
Map内存开销,O (n) 极简循环; - 乱序、重排、大量增删自动降级慢路径
Map查表,空间换时间 O (1) 匹配;天然适配前端两种典型列表场景(静态列表 / 拖拽排序列表)
- 稳定有序列表走快路径线性遍历 ,无
- 移动算法
lastPlacedIndex最小化DOM移动 :通过记录已摆放旧节点最大下标,仅当旧下标小于基准值时标记移动,避免不必要的DOM重排,是列表Diff核心优化
reconcileChildrenIterable
专门处理可迭代对象 (Set、生成器、自定义迭代器、异步迭代器等非数组列表)子节点调和。实现思路:
- 提取迭代器函数,拿到迭代器对象;
- 把迭代器透传给
reconcileChildrenIterator,复用数组Diff整套快慢双路径逻辑; - 本质是适配器包装层 ,不实现完整
Diff,仅做类型适配,抹平【数组】与【可迭代对象】的上层差异
javascript
function reconcileChildrenIteratable(
returnFiber,
currentFirstChild,
newChildrenIterable,
lanes
) {
// 读取对象的 Symbol.iterator 属性,返回迭代器生成函数
const iteratorFn = getIteratorFn(newChildrenIterable);
// 合法性校验,非法可迭代直接抛内部错误
if (typeof iteratorFn !== 'function') {
throw new Error(
'An object is not an iterable. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
// 执行迭代器工厂,生成迭代器实例
const newChildren = iteratorFn.call(newChildrenIterable);
// 把标准化后的迭代器交给真正的 Diff 核心函数,最终返回构建完成的新子 Fiber 链表头
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChildren,
lanes
);
}
作用
- 提取传入可迭代对象的迭代器工厂函数
- 校验迭代器工厂合法性,非法类型抛出内部异常
- 调用工厂生成标准迭代器实例
- 将父
Fiber、旧子节点、迭代器、优先级全部透传给迭代器专用Diff函数,返回调和后的新子链表
reconcileChildrenAsyncIteratable
异步可迭代对象(AsyncIterable) 的适配调和入口,专门处理带有 Symbol.asyncIterator 的异步列表(异步生成器、异步集合等)。核心逻辑:
- 取出异步迭代器
- 包装一层同步
next代理,自动解包Promise; - 转成普通同步迭代器格式,复用
reconcileChildrenIterator已有的完整列表Diff逻辑,不需要重写一套异步Diff - 依赖:
unwrapThenable用于剥离Promise,取出内部同步迭代结果
javascript
function reconcileChildrenAsyncIteratable(
returnFiber,
currentFirstChild,
newChildrenIterable, // 异步可迭代对象(实现 [ASYNC_ITERATOR])
lanes,
) {
// 调用异步可迭代对象的异步迭代器工厂方法,获取异步迭代器实例
const newChildren = newChildrenIterable[ASYNC_ITERATOR]();
// 合法性校验
if (newChildren == null) {
throw new Error('An iterable object provided no iterator.');
}
// 新建一个同步迭代器外壳,仅暴露标准同步 next() 方法,对外表现为普通同步迭代器
const iterator = {
next() {
return unwrapThenable(newChildren.next());
},
};
// 将父 Fiber、旧子节点、包装后的同步迭代器、渲染优先级全部传入通用迭代器,直接返回其调和结果(新子 Fiber 链表头)
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
iterator,
lanes
);
}
作用
- 调用异步可迭代对象的异步迭代器工厂,获取底层
AsyncIterator - 校验迭代器是否存在,不存在则抛出运行时错误
- 创建代理同步迭代器,内部调用异步迭代器
next,并通过unwrapThenable解包Promise,抹平异步接口差异 - 将包装后的标准同步迭代器传入通用迭代器
Diff函数,复用整套列表调和逻辑并返回新子链表
reconcileChildrenIterator
迭代器列表 Diff 通用核心实现 ,是 reconcileChildrenArray 的迭代器版本,逻辑几乎完全对齐,仅两处差异:
- 数据源遍历方式:数组靠下标
newChildren[newIdx],迭代器靠newChildren.next()获取step.value - 循环终止标记:数组判断
newIdx < length,迭代器判断step.done
ini
function reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChildren,
lanes,
) {
if (newChildren == null) {
throw new Error('An iterable object provided no iterator.');
}
let resultingFirstChild = null;
let previousNewFiber = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// 首次调用迭代器 next() ,拿到第一个迭代项 step,包含 done 终止标记与子节点值 step.value
let step = newChildren.next();
for (; oldFiber !== null && !step.done; newIdx++, step = newChildren.next()) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 迭代器遍历完成
if (step.done) {
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
if (oldFiber === null) {
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = createChild(returnFiber, step.value, lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
const existingChildren = mapRemainingChildren(oldFiber);
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = updateFromMap(existingChildren, returnFiber, newIdx, step.value, lanes);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
const currentFiber = newFiber.alternate;
if (currentFiber !== null) {
if (enableOptimisticKey && currentFiber.key === REACT_OPTIMISTIC_KEY) {
existingChildren.delete(-newIdx - 1);
} else {
existingChildren.delete(currentFiber.key === null ? newIdx : currentFiber.key);
}
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
分类十、入口函数组
reconcileChildFibers
父节点调和子节点的顶层入口包装函数 ,真实子节点匹配逻辑全部封装在内部 reconcileChildFibersImpl;本函数只做三件事:
- 初始化全局异步计数变量
- 捕获调和过程抛出的异常,区分
Suspense/ 异步Promise可重抛异常和普通运行时错误 - 普通错误转换成
Throw类型Fiber,作为子节点返回,让ErrorBoundary捕获渲染降级
csharp
function reconcileChildFibers(
returnFiber,
currentFirstChild,
newChild,
lanes,
) {
try {
// 全局计数器,用于递归处理 Promise 子节点时生成稳定唯一索引
// 隔离不同父节点下异步子节点的索引命名空间
thenableIndexCounter = 0;
const firstChildFiber = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
// 全局状态,缓存异步子节点解析过程临时数据
// 调和正常走完无异常,清空全局异步临时状态,释放内存,避免下一轮渲染残留脏数据
thenableState = null;
// 将构建完成的新子 Fiber 链表头返回给父节点,挂载到 workInProgress.child
return firstChildFiber;
} catch (x) {
// 1.资源加载触发的标准挂起异常,需要向上冒泡匹配最近的 <Suspense> 边界,渲染 fallback
// 2.并发更新动作触发的挂起异常,同样交给 Suspense 处理
// 3.兼容旧版同步模式 Promise 抛出------------未关闭旧兼容模式且当前父节点非并发模式且捕获到的值是 Promise thenable 对象
if (
x === SuspenseException ||
x === SuspenseActionException ||
(!disableLegacyMode &&
(returnFiber.mode & ConcurrentMode) === NoMode &&
typeof x === 'object' &&
x !== null &&
typeof x.then === 'function')
) {
// Suspense 依靠抛出异常中断当前渲染、切换 fallback,捕获后重抛维持原有调度链路,不破坏并发 / 同步挂起逻辑
throw x;
}
// 同步渲染阶段捕获的普通 JS 错误转换成合法 Fiber 节点
// 创建专门承载报错信息的错误 Fiber,供 ErrorBoundary 捕获
const throwFiber = createFiberFromThrow(x, returnFiber.mode, lanes);
// 绑定父子指针,纳入 Fiber 树结构
throwFiber.return = returnFiber;
// 不抛出异常,而是把错误 Fiber 作为父节点的子节点返回
return throwFiber;
} finally {
}
}
作用
- 进入
try块,重置全局异步子节点索引计数器thenableIndexCounter = 0 - 调用
reconcileChildFibersImpl执行完整子节点类型分发与Diff调和,得到新子链表头 - 调和无异常时清空全局异步临时状态
thenableState = null,返回新子Fiber - 若调和过程抛出异常,进入
catch分支判断异常类型- 若是
Suspense挂起异常 / 旧同步模式Promise:原样重新抛出,交由上层Suspense调度处理 - 普通业务代码错误:创建承载错误的
Throw Fiber,绑定父指针,将该错误Fiber作为子节点返回,供ErrorBoundary捕获
- 若是
reconcileChildFibersImpl
子节点调和核心分发器 ,承接外层 reconcileChildFibers,唯一职责:根据 newChild 的 JSX 原始类型,分发到对应专门 Diff 函数(单元素 / 单文本 / 数组 / 迭代器 / Portal/Lazy/Promise/Context),生成新子 Fiber 链表头返回。前置关键规则:
- 顶层无
key、无ref Fragment直接解包,用内部children替代自身 - 按对象复杂类型 → 基础文本数字顺序分层判断
- 递归解包
Lazy、Promise、Context,直到拿到真实可渲染节点 - 单节点统一用
placeSingleChild处理插入标记 - 空 / 非法子节点直接删除全部旧子节点
ini
function reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
newChild,
lanes,
) {
// 顶层无 key 无 ref 的 Fragment 自动解包
const isUnkeyedUnrefedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null &&
(enableFragmentRefs ? newChild.props.ref === undefined : true);
if (isUnkeyedUnrefedTopLevelFragment) {
validateFragmentProps(newChild, null, returnFiber);
// 直接把 newChild 替换为 Fragment 内部 children,丢弃外层 Fragment 节点
newChild = newChild.props.children;
}
// 统一处理所有对象类型 newChild
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
// 普通 DOM / 组件 JSX 元素
case REACT_ELEMENT_TYPE: {
// 调用 reconcileSingleElement 执行单元素 Diff
// 全新节点打上 Placement 插入标记
const firstChild = placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes),
);
// 返回生成的单根 Fiber
return firstChild;
}
// createPortal
case REACT_PORTAL_TYPE:
// 调用 reconcileSinglePortal 做单 Portal 调和,同样走单节点替换逻辑返回
return placeSingleChild(
reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes),
);
// 懒加载组件
case REACT_LAZY_TYPE: {
// 执行懒加载导入,拿到真实组件元素
const result = resolveLazy(newChild);
// 递归调用自身,用解析后的真实元素重新走完整分发逻辑
const firstChild = reconcileChildFibersImpl(returnFiber, currentFirstChild, result, lanes);
return firstChild;
}
}
// 普通数组
if (isArray(newChild)) {
// 调用数组快慢双路径 Diff 函数 reconcileChildrenArray
const firstChild = reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
// 返回新子链表头
return firstChild;
}
// 同步可迭代对象(Set、生成器等)
if (getIteratorFn(newChild)) {
// 交给同步迭代器适配器,底层复用通用迭代器统一 Diff
const firstChild = reconcileChildrenIteratable(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
return firstChild;
}
// 异步可迭代对象
if (enableAsyncIterableChildren && typeof newChild[ASYNC_ITERATOR] === 'function') {
// 交给异步迭代器适配器,底层复用通用迭代器统一 Diff
const firstChild = reconcileChildrenAsyncIteratable(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
return firstChild;
}
// 异步子节点
if (typeof newChild.then === 'function') {
const thenable = newChild
// 递归调用自身,重新分发解析后的子节点
const firstChild = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
unwrapThenable(thenable), // 取出 Promise 内部真实渲染值
lanes,
);
return firstChild;
}
// Context 对象({useContext () 返回的 Context 实例直接作为 children)
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context = newChild;
// 递归用读取到的值重新分发
return reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
readContextDuringReconciliation(returnFiber, context, lanes), // 读取上下文值
lanes,
);
}
// 非法对象兜底
throwOnInvalidObjectType(returnFiber, newChild);
}
// 基础文本 / 数字 /bigint
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
// 调用 reconcileSingleTextNode 调和单个文本节点,单节点替换后返回
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes),
);
}
// 空值兜底,把父节点下全部旧子 Fiber 标记删除,返回 null(无子节点)
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
作用
- 先判断顶层无
key无ref Fragment,自动解包替换newChild; - 判断是否为非空对象
- 匹配内置
$$typeof:Element/Portal/Lazy(递归解包) - 数组 → 数组
Diff - 同步迭代器 → 同步迭代适配器
- 异步迭代器 → 异步迭代适配器
Promise→ 解包后递归Context→ 读值后递归- 其他普通对象 → 抛非法类型错误
- 匹配内置
- 匹配字符串 / 数字 /
bigint→ 单文本Diff - 其余空 / 布尔类型 → 删除全部旧子节点,返回
null
三、设计思想
Diff核心规则 :key优先匹配同类型节点,尽可能复用旧fiber,最小化DOM移动 / 创建 / 删除- 快慢双 Diff 路径 :头部优先扫描 ,先从左到右顺序匹配(现实场景中列表头部变动最少),匹配中断才切到
Map模式- 快路径 :新旧子节点一一顺序遍历,
key逐个匹配updateSlot; - 慢路径 :旧子节点存入
Map,新子节点查表匹配updateFromMap,用于乱序、删除新增较多场景
- 快路径 :新旧子节点一一顺序遍历,
- 空间换时间 :牺牲少量内存提升乱序列表
Diff匹配效率 - 最少移动量 :
lastPlacedIndex算法确保list重排时DOM移动次数最小化