reconcileChildren 源码解析

beginWork 调和阶段,接收Fiber、旧子 Fiber 链表、新 JSX children、渲染优先级 lanes ,完成新旧子节点 FiberDiff,输出全新 workInProgress 子单向链表头节点,实现三大核心目标:

  • 尽可能复用旧 Fiber 实例,减少新建 / 销毁
  • 标记 DOM 变更副作用(新增 Placement、删除 ChildDeletion、移动)
  • 构建符合新 JSX 结构的子 Fiber 链表,挂载到父 Fiberchild 字段
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 nullmount 无旧节点) 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 加入父 fiberreturnFiber)的 deletions 数组,同时在父 fiber 上设置 ChildDeletion flag

设计意义

  • 副作用收集延迟执行 :不在 reconcile 阶段立即删除 fiberDOM,仅记录,统一聚合到父节点。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>。后续每个新节点通过 keyindexMap 中 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,重置 indexsibling

设计意义

  • 最大化复用现有 fiber 实例 :不是销毁再创建,而是复用 fiber 对象的 stateNodeDOM 节点/组件实例),只更新 pendingProps,减少内存分配
  • index/sibling 重置index=0sibling=null 是因为 placeChildreconcileChildrenArray 会在后续重新设置这些值------在 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 记录上一个已经摆放完成的最大旧下标;当新 fiberoldIndex >= lastPlacedIndex 时,说明此节点在旧列表中的位置"足够靠后",不需要移动。当 oldIndex < lastPlacedIndex 时,此节点需要被移动到当前 newIndex 的位置

设计意义

最少移动量算法 :在从左到右扫描新列表时,如果旧节点的索引不断增大,那这些节点在新列表中的相对顺序与旧列表一致,无需移动。只有当旧索引回退时(oldIndex < lastPlacedIndex),才需要移动


placeSingleChild单个子节点

javascript 复制代码
function placeSingleChild(newFiber) {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement | PlacementDEV;
  }
  return newFiber;
}

作用

单节点场景无需比较下标,仅区分全新节点标记插入,复用节点不标记


分类四、各类节点更新复用

统一逻辑:存在同类型旧 fiberuseFiber 复用;不存在 → 创建全新 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
  • 存在匹配旧文本 fiberuseFiber 复用,更新文本 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/bigintcreateFiberFromText
  • ReactElementcreateFiberFromElement
  • PortalcreateFiberFromPortal
  • Lazy 组件:递归解析 lazy 后重新 createChild
  • 数组 / 迭代器 / 异步迭代器:创建 Fragment 包裹
  • Promise thenable:解包后递归创建
  • Context 对象:读取上下文值后递归创建
  • 非法类型:返回 null

设计意义

createChildupdateSlot/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 的第一阶段(顺序匹配)中,检查新子节点是否与当前旧 fiberkey 匹配。匹配则复用,不匹配返回 null 中断顺序匹配

  • 对比新旧 keykey 完全一致才尝试复用;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 则复用,否则等同于新建
  • 兼容文本、ElementPortalLazyPromiseContext 所有子类型

设计意义 顺序匹配断裂后的"回退查找器"。不再假设 oldFibernewChild[newIdx] 位置对应,而是通过 key 在剩余旧节点中精确查找可复用 fiber。快路径匹配失败后降级到 Map 查表,保证任意列表结构都能正确 Diff,兼顾两种极端场景性能


分类八、单元素子节点调和

reconcileSingleElement

当父节点新 children单个 ReactElement(<div />/ 组件 / Fragment) 时调用,用于:

  • 在旧子 fiber 链表中按 key 寻找可复用旧 fiber
  • 匹配成功:校验类型一致,删除其余兄弟旧节点,复用 fiber
  • key 匹配但类型不兼容:删除整条旧子链表,新建 fiber
  • key 全部不匹配:逐个删除所有旧子节点,最后创建全新 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
  • 三重校验:keytag、挂载容器、实现对象全部一致才可复用
  • 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。文本节点没有 keyDiff 规则极简:

  • 旧第一个子节点恰好是 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 中未复用的旧节点,标记删除,构建全新 workInProgressFiber 链表并返回
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;
}

作用

  • 初始化新旧链表遍历指针、新链表头尾标记、移动算法基准下标
  • 快路径线性顺序遍历新旧列表,updateSlotkey 顺序匹配
    • 匹配成功:标记移动 / 插入副作用,串联新 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,唯一职责:根据 newChildJSX 原始类型,分发到对应专门 Diff 函数(单元素 / 单文本 / 数组 / 迭代器 / Portal/Lazy/Promise/Context),生成新子 Fiber 链表头返回。前置关键规则:

  • 顶层无 key、无 ref Fragment 直接解包,用内部 children 替代自身
  • 按对象复杂类型 → 基础文本数字顺序分层判断
  • 递归解包 LazyPromiseContext,直到拿到真实可渲染节点
  • 单节点统一用 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);
}

作用

  • 先判断顶层无 keyref Fragment,自动解包替换 newChild
  • 判断是否为非空对象
    • 匹配内置 $$typeofElement / Portal / Lazy(递归解包)
    • 数组 → 数组 Diff
    • 同步迭代器 → 同步迭代适配器
    • 异步迭代器 → 异步迭代适配器
    • Promise → 解包后递归
    • Context → 读值后递归
    • 其他普通对象 → 抛非法类型错误
  • 匹配字符串 / 数字 / bigint → 单文本 Diff
  • 其余空 / 布尔类型 → 删除全部旧子节点,返回 null

三、设计思想

  • Diff 核心规则key 优先匹配同类型节点,尽可能复用旧 fiber,最小化 DOM 移动 / 创建 / 删除
  • 快慢双 Diff 路径头部优先扫描 ,先从左到右顺序匹配(现实场景中列表头部变动最少),匹配中断才切到 Map 模式
    • 快路径 :新旧子节点一一顺序遍历,key 逐个匹配 updateSlot
    • 慢路径 :旧子节点存入 Map,新子节点查表匹配 updateFromMap,用于乱序、删除新增较多场景
  • 空间换时间 :牺牲少量内存提升乱序列表 Diff 匹配效率
  • 最少移动量lastPlacedIndex 算法确保 list 重排时 DOM 移动次数最小化
相关推荐
还有多久拿退休金19 小时前
Ant Design Tree 搜索定位避坑指南:虚拟滚动下如何实现高亮与精准定位
前端·react.js
光影少年21 小时前
react 原理与进阶
前端·react.js·掘金·金石计划
饼饼饼1 天前
React19 状态解惑:State 没那么神秘,一文读懂 React 状态不可变原则与 Hooks 底层链表
前端·react.js
花椒技术1 天前
RN 多包热更新实践:更新校验、运行时加载与 Bridge 缓存治理
react native·react.js·harmonyos
互联网推荐官1 天前
上海 APP 开发服务甄选:技术架构设计、全维度判断框架
javascript·react native·react.js·app开发·开发经验·上海
饼饼饼2 天前
React19 新手指南:JSX 没那么难,用好这几条规则就够了
前端·javascript·react.js
光影少年2 天前
react大列表优化:虚拟列表原理
前端·javascript·react.js
weedsfly2 天前
React 开发中的闭包陷阱:四个真实场景,让你彻底理解闭包
前端·react.js