React18代码的探索(五)

12 阅读9分钟

React18代码的探索(四)

diff算法核心函数reconcileChildren

reconcileChildren函数是实现Virtual DOM diff算法的核心载体,主要负责在组件更新时比较新旧子元素的差异,并生成新的fiber

参数current代表当前组件的Fiber节点,workInProgress是正在构建的新Fiber树,nextChildren是新的子元素,renderLanes是优先级相关的车道

首次渲染时,没有current,因此直接挂载子节点,而更新时则需要协调新旧子节点

更新前:<div><span key="a"/></div>
更新后:<div><p key="b"/><span key="a"/></div>

协调过程:
1. 检测key="b"新节点 → 创建新Fiber
2. 发现key="a"节点位置变化 → 标记Placement
3. 旧span节点 → 复用并移动位置

该函数在React渲染流程中的定位

beginWork阶段 → reconcileChildren → 生成子Fiber树
                        ↓
    实现Virtual DOM diff算法的核心载体
graph TD
    A[开始] --> B{current存在?}
    B -->|是| C[协调更新模式]
    B -->|否| D[初始挂载模式]
    C --> E[调用reconcileChildFibers]
    D --> F[调用mountChildFibers]
    E & F --> G[返回子Fiber节点]
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
    // 挂载阶段处理
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // 更新阶段处理
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

ChildReconciler工厂函数

这两个不同阶段的fiber处理都是调用的ChildReconciler函数,区别是shouldTrackSideEffects入参的区别

  • mountChildFibers -> false

  • reconcileChildFibers -> true

ChildReconciler是个工厂函数,函数内部定义了deleteChild、deleteRemainingChildren、mapRemainingChildren等方法,还有处理文本节点、元素、片段的update函数。这些方法共同负责处理子节点的增删改,以及Fiber节点的复用和标记副作用,对外暴露了reconcileChildFibers函数,也是个处理函数

函数reconcileChildFibers的主要职责是协调子Fiber节点,处理不同类型的子元素,如React元素、Portal、懒加载组件、数组、迭代器等。函数接收四个参数:returnFiber(父Fiber)、currentFirstChild(当前第一个子Fiber)、newChild(新的子元素)、lanes(优先级车道)

graph TD
    A[开始] --> B{是否无key Fragment?}
    B -->|是| C[展开子节点]
    B -->|否| D{类型判断}
    D -->|React元素| E[单元素协调]
    D -->|Portal| F[Portal协调]
    D -->|懒加载| G[递归解析]
    D -->|数组| H[数组协调]
    D -->|迭代器| I[迭代器协调]
    D -->|文本| J[文本节点处理]
    E & F & G & H & I & J --> K[标记位置]
    K --> L{是否有效节点?}
    L -->|否| M[删除旧节点]
    L -->|是| N[返回新Fiber树]
function ChildReconciler(shouldTrackSideEffects) {
  // 核心协调方法集合:
  // 1. 节点删除逻辑
  function deleteChild(returnFiber, childToDelete) {
    if (shouldTrackSideEffects) {
      // 维护父Fiber的deletions数组
      returnFiber.deletions = returnFiber.deletions || []
      returnFiber.deletions.push(childToDelete)
      returnFiber.flags |= ChildDeletion // 标记删除操作
    }
  }

  // 2. 节点复用机制
  function useFiber(fiber, pendingProps) {
    const clone = createWorkInProgress(fiber, pendingProps)
    clone.index = 0  // 重置索引
    clone.sibling = null // 断开兄弟节点
    return clone
  }

  // 3. 位置标记算法
  function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex
    const current = newFiber.alternate
    if (current) {
      // 移动判断逻辑
      if (current.index < lastPlacedIndex) {
        newFiber.flags |= Placement // 标记移动
        return lastPlacedIndex
      }
      return current.index // 保持原位
    } else {
      newFiber.flags |= Placement // 标记插入
      return lastPlacedIndex
    }
  }

  // 4. 多种节点类型处理
  const updateFunctions = {
    Text: updateTextNode,
    Element: updateElement,
    Portal: updatePortal,
    Fragment: updateFragment
  }

  function reconcileChildFibers(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      newChild: any,
      lanes: Lanes,
    ): Fiber | null {
      // 处理无key的顶层Fragment
      const isUnkeyedTopLevelFragment = ...;
      if (isUnkeyedTopLevelFragment) {
        newChild = newChild.props.children; // 展开Fragment子节点
      }

      // 核心协调逻辑分支
      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE: // React元素处理
            return reconcileSingleElement(...); 
          case REACT_PORTAL_TYPE:  // Portal处理
            return reconcileSinglePortal(...);
          case REACT_LAZY_TYPE:    // 懒加载组件处理
            return reconcileChildFibers(...); // 递归解析
        }

        // 数组/迭代器处理
        if (isArray(newChild)) return reconcileChildrenArray(...);
        if (getIteratorFn(newChild)) return reconcileChildrenIterator(...);

        throwOnInvalidObjectType(...); // 非法对象类型报错
      }

      // 文本节点处理
      if (isStringOrNumber(newChild)) {
        return reconcileSingleTextNode(...);
      }

      // 清空剩余子节点
      return deleteRemainingChildren(...);
    }
  return reconcileChildFibers
}

下面我们就详细的看看不同种类的协调逻辑

reconcileSingleElement 协调React元素

该函数负责处理单个React元素,函数接收returnFiber、currentFirstChild、element和lanes作为参数。它的主要任务是在现有的子Fiber节点中查找是否有可以复用的节点,如果没有则创建新的Fiber节点

函数开始通过element.key进行循环查找currentFirstChild的子节点。如果找到key相同的子节点,就会检查其类型是否匹配。这里有两种情况:处理Fragment类型和其他元素类型。对于Fragment,需要验证子节点的标签是否为Fragment,然后复用现有的Fiber节点。对于其他元素类型,会检查elementType是否一致,或者是否属于热更新的兼容情况,或者是懒加载组件。如果匹配,则删除剩余的兄弟节点,并复用现有的Fiber。

如果没有找到匹配的key,则删除当前子节点,继续查找下一个兄弟节点。如果循环结束后仍未找到,就会创建新的Fiber节点。这里分Fragment和其他元素两种情况处理,分别调用不同的创建函数

最后,函数的返回值是新的或复用的Fiber节点,并正确设置其return指针,确保Fiber树的连接正确

单元素协调入口 → reconcileSingleElement → 复用或创建Fiber
                        ↓
        实现O(n)复杂度的key匹配优化算法
graph TD
    A[开始] --> B{遍历子节点}
    B --> C{key匹配?}
    C -->|是| D{类型匹配?}
    D -->|是| E[复用Fiber并返回]
    D -->|否| F[删除所有子节点]
    C -->|否| G[删除当前节点]
    B --> H[创建新Fiber]
    E & F & G & H --> I[返回新Fiber]
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  // 核心协调流程:
  const key = element.key;
  let child = currentFirstChild;
  
  // 阶段1:遍历现有子节点寻找可复用节点
  while (child !== null) {
    if (child.key === key) { // Key匹配
      const elementType = element.type;
      
      // 处理Fragment类型
      if (elementType === REACT_FRAGMENT_TYPE) {
        if (child.tag === Fragment) { // Fragment类型匹配
          // 删除剩余兄弟节点
          deleteRemainingChildren(returnFiber, child.sibling);
          // 复用现有Fiber节点
          const existing = useFiber(child, element.props.children);
          existing.return = returnFiber;
          // 开发模式调试信息
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      } else {
        // 类型匹配检查(含热更新兼容)
        if (child.elementType === elementType || 
            (__DEV__ && isCompatibleFamilyForHotReloading(child, element)) ||
            (isLazyComponent(elementType) && resolveLazy(elementType) === child.type)) {
          
          // 清理后续兄弟节点
          deleteRemainingChildren(returnFiber, child.sibling);
          // 复用现有Fiber
          const existing = useFiber(child, element.props);
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber;
          // 开发模式调试信息
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      }
      // Key匹配但类型不匹配 → 删除所有子节点
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      // Key不匹配 → 删除当前子节点
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }

  // 阶段2:创建新Fiber节点
  if (element.type === REACT_FRAGMENT_TYPE) {
    // 创建Fragment类型的Fiber
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      lanes,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    // 创建普通元素类型的Fiber
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

reconcileChildrenArray 子元素数组的更新

该函数无法进行两端优化,因为没有反向指针,所以主要采用前向遍历和映射查找的方式

函数分为几个阶段:初始遍历新旧子节点,处理剩余的新节点,以及使用映射处理移动和删除

graph TD
    A[开始] --> B{阶段1顺序遍历}
    B -->|匹配| C[复用节点]
    B -->|不匹配| D[阶段2快速插入]
    C --> E[标记位置]
    E --> B
    D --> F{旧节点耗尽?}
    F -->|是| G[创建新节点]
    F -->|否| H[阶段3映射匹配]
    G --> I[标记插入]
    H --> J[建立旧节点映射]
    J --> K[遍历新节点]
    K --> L{存在复用?}
    L -->|是| M[移动节点]
    L -->|否| N[创建新节点]
    M --> O[更新映射表]
    O --> K
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  lanes: Lanes,
): Fiber | null {
  // 核心流程分解为三个阶段:
  
  // 阶段1:顺序匹配(新旧节点同步遍历)
  for (; oldFiber && newIdx < newChildren.length; newIdx++) {
    if (oldFiber.index > newIdx) {
      // 索引跳跃处理
      nextOldFiber = oldFiber;
      oldFiber = null;
    }
    const newFiber = updateSlot(...); // 尝试复用节点
    if (!newFiber) break; // 不匹配时进入下一阶段
    deleteChild(...);     // 清理无法复用的旧节点
    placeChild(...);      // 标记位置变化
  }

  // 阶段2:批量插入新节点(当旧节点已耗尽)
  if (!oldFiber) {
    for (; newIdx < newChildren.length; newIdx++) {
      createChild(...);   // 创建新Fiber节点
      placeChild(...);    // 标记插入位置
    }
  }

  // 阶段3:映射匹配剩余节点(处理移动/删除操作)
  const existingChildren = mapRemainingChildren(...);
  for (; newIdx < newChildren.length; newIdx++) {
    updateFromMap(...);   // 从映射表查找可复用节点
    existingChildren.delete(...); // 移除已匹配节点
    placeChild(...);      // 更新位置标记
  }
  existingChildren.forEach(...); // 删除未匹配的旧节点
}

completeUnitOfWork

completeUnitOfWork函数属于工作循环的一部分,负责完成当前工作单元并处理可能的错误和兄弟节点

函数completeUnitOfWork接收一个unitOfWork参数,进入一个do-while循环,处理当前的工作单元。代码分为两部分:正常完成的情况和出现错误的情况

在正常流程中,函数调用completeWork来处理当前Fiber节点,如果返回了新的工作(next不为null),则立即处理。在错误情况下,调用unwindWork进行回滚,并处理可能的错误边界

graph TD
 A[开始] --> B{当前节点是否正常完成?}
 B -->|是| C[执行completeWork收集副作用]
 B -->|否| D[执行unwindWork错误处理]
 C --> E{生成新工作节点?}
 E -->|是| F[立即处理新节点]
 D --> G{生成新工作节点?}
 G -->|是| H[处理新节点]
 F & H --> I[处理兄弟节点]
 I --> J{有兄弟节点?}
 J -->|是| K[处理兄弟节点]
 J -->|否| L[回溯父节点]
 L --> M{是否到达根节点?}
 M -->|是| N[标记根完成]
function completeUnitOfWork(unitOfWork: Fiber): void {
 let completedWork = unitOfWork;  // 初始化当前工作单元
 do {
   const current = completedWork.alternate;  // 获取已提交的对应Fiber节点
   const returnFiber = completedWork.return; // 获取父Fiber节点

   // 检查节点是否正常完成(无错误标志)
   if ((completedWork.flags & Incomplete) === NoFlags) {
     setCurrentDebugFiberInDEV(completedWork);  // 开发环境设置调试标记

     let next;
     if (!enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode) {
       next = completeWork(current, completedWork, subtreeRenderLanes);  // 非性能分析模式
     } else {
       startProfilerTimer(completedWork);  // 启动性能分析计时器
       next = completeWork(current, completedWork, subtreeRenderLanes);
       stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);  // 停止并记录耗时
     }

     resetCurrentDebugFiberInDEV();  // 重置调试标记

     if (next !== null) {  // 如果生成新工作单元
       workInProgress = next;  // 更新全局工作指针
       return;  // 优先处理新任务
     }
   } else {
     // 错误处理流程
     const next = unwindWork(current, completedWork, subtreeRenderLanes);  // 执行错误边界处理

     if (next !== null) {  
       next.flags &= HostEffectMask;  // 保留宿主环境相关副作用
       workInProgress = next;  // 更新工作指针继续处理
       return;
     }

     // 性能分析相关错误处理
     if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
       let actualDuration = completedWork.actualDuration;
       let child = completedWork.child;  // 累加所有子节点耗时
       while (child !== null) {
         actualDuration += child.actualDuration;
         child = child.sibling;
       }
       completedWork.actualDuration = actualDuration;  // 更新总耗时
     }

     if (returnFiber !== null) {
       returnFiber.flags |= Incomplete;  // 标记父节点未完成
       returnFiber.subtreeFlags = NoFlags;  // 清空子树标志
       returnFiber.deletions = null;  // 重置删除队列
     } else {  // 已回溯到根节点
       workInProgressRootExitStatus = RootDidNotComplete;  // 设置根状态
       workInProgress = null;
       return;
     }
   }

   const siblingFiber = completedWork.sibling;  // 获取兄弟节点
   if (siblingFiber !== null) {
     workInProgress = siblingFiber;  // 优先处理兄弟节点
     return;
   }

   // 回溯至父节点
   completedWork = returnFiber;  
   workInProgress = completedWork;  // 更新工作指针
 } while (completedWork !== null);  // 循环直到根节点

 // 完成整个根节点的处理
 if (workInProgressRootExitStatus === RootInProgress) {
   workInProgressRootExitStatus = RootCompleted;  // 更新根状态为完成
 }
}

completeWork

该函数在React渲染流程中的作用定位

协调阶段 → beginWork → completeWork
              ↓
  完成DOM准备/属性收集/副作用标记

函数completeWork根据不同的Fiber类型处理各节点的完成工作,比如HostRoot、HostComponent、HostText等。每个case处理不同类型的组件,如类组件、函数组件、Host组件等

graph TD
   A[开始] --> B{当前Fiber类型}
   B -->|HostRoot| C[处理根节点状态]
   B -->|HostComponent| D[创建/更新DOM节点]
   B -->|HostText| E[处理文本更新]
   B -->|Suspense| F[处理挂起状态]
   C & D & E & F --> G[属性冒泡]
   G --> H[返回null继续遍历]
function completeWork(
 current: Fiber | null,
 workInProgress: Fiber,
 renderLanes: Lanes,
): Fiber | null {
 // 核心处理逻辑分三个阶段:
 // 1. 上下文管理 - 弹出当前Fiber的树上下文
 popTreeContext(workInProgress); 

 // 2. 分类型处理(核心switch结构)
 switch (workInProgress.tag) {
   // 函数组件/类组件等通用处理
   case FunctionComponent:
   case ClassComponent: {
     // 处理旧版上下文
     if (isLegacyContextProvider(Component)) {
       popLegacyContext(workInProgress);
     }
     // 属性冒泡机制
     bubbleProperties(workInProgress);
     return null;
   }

   // 根节点处理
   case HostRoot: {
     // 处理过渡追踪(Transition Tracing)
     if (enableTransitionTracing) {
       const transitions = getWorkInProgressTransitions();
       if (transitions !== null) {
         workInProgress.flags |= Passive; // 标记被动效果
       }
     }

     // 缓存处理逻辑
     if (enableCache) {
       // 比较新旧缓存差异
       if (cache !== previousCache) {
         workInProgress.flags |= Passive;
       }
       popCacheProvider(workInProgress, cache);
     }

     // 处理hydration状态
     const wasHydrated = popHydrationState(workInProgress);
     if (wasHydrated) {
       markUpdate(workInProgress); // 标记需要更新
     }
   }

   // DOM元素处理
   case HostComponent: {
     // 创建/更新DOM实例
     const instance = createInstance(
       type,
       newProps,
       rootContainerInstance,
       currentHostContext,
       workInProgress,
     );

     // 子节点追加策略
     appendAllChildren(instance, workInProgress, false, false);

     // 初始化DOM属性
     if (finalizeInitialChildren(...)) {
       markUpdate(workInProgress);
     }

     // Ref处理
     if (workInProgress.ref !== null) {
       markRef(workInProgress);
     }
   }

   // 文本节点处理
   case HostText: {
     // 文本内容对比更新
     if (oldText !== newText) {
       workInProgress.stateNode = createTextInstance(...);
       markUpdate(workInProgress);
     }
   }

   // Suspense组件处理
   case SuspenseComponent: {
     // 脱水边界处理
     if (current?.memoizedState?.dehydrated) {
       const fallthrough = completeDehydratedSuspenseBoundary(...);
       if (!fallthrough) return workInProgress;
     }

     // 挂起状态切换处理
     if (nextDidTimeout !== prevDidTimeout) {
       // 可见性状态更新
       offscreenFiber.flags |= Visibility;
       // 并发模式下的渲染控制
       renderDidSuspendDelayIfPossible();
     }
   }
 }

 // 3. 通用属性冒泡(所有类型最终调用)
 bubbleProperties(workInProgress);
}

renderRootSync 同步渲染

  • 无任务分片(与并发模式 workLoopConcurrent 对比)
  • 无时间切片( shouldYield 始终返回false)
  • 无优先级中断机制
更新调度 → renderRootSync → workLoopSync → commitRoot
                     ↓
        完成同步模式下的Fiber树协调
graph TD
   A[开始] --> B[保存上下文]
   B --> C{是否需新工作栈?}
   C -->|是| D[初始化工作栈]
   C -->|否| E[进入渲染循环]
   D --> E
   E --> F[执行workLoopSync]
   F --> G{是否报错?}
   G -->|是| H[处理错误]
   G -->|否| I[清理资源]
   H --> E
   I --> J[完整性检查]
   J --> K[返回结果状态]
function renderRootSync(root: FiberRoot, lanes: Lanes) {
 // 保存当前执行上下文和Dispatcher
 const prevExecutionContext = executionContext;
 executionContext |= RenderContext;  // 标记进入渲染阶段
 const prevDispatcher = pushDispatcher();  // 切换当前Dispatcher

 // 初始化工作堆栈
 if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
   if (enableUpdaterTracking) {  // 开发工具跟踪逻辑
     if (isDevToolsPresent) {
       // 处理更新队列缓存
       const memoizedUpdaters = root.memoizedUpdaters;
       if (memoizedUpdaters.size > 0) {
         restorePendingUpdaters(root, workInProgressRootRenderLanes);
         memoizedUpdaters.clear();
       }
       // 移动待处理Fiber到缓存
       movePendingFibersToMemoized(root, lanes);
     }
   }

   workInProgressTransitions = getTransitionsForLanes(root, lanes);  // 获取过渡追踪
   prepareFreshStack(root, lanes);  // 准备新工作栈
 }

 // 开发模式调试标记
 if (__DEV__) {
   if (enableDebugTracing) logRenderStarted(lanes);
 }

 // 性能分析标记
 if (enableSchedulingProfiler) markRenderStarted(lanes);

 // 核心渲染循环
 do {
   try {
     workLoopSync();  // 同步工作循环
     break;
   } catch (thrownValue) {
     handleError(root, thrownValue);  // 错误处理
   }
 } while (true);

 // 清理阶段
 resetContextDependencies();  // 重置上下文依赖
 executionContext = prevExecutionContext;  // 恢复执行上下文
 popDispatcher(prevDispatcher);  // 恢复原始Dispatcher

 // 完整性检查
 if (workInProgress !== null) {
   throw new Error('Cannot commit an incomplete root');
 }

 // 开发模式结束标记
 if (__DEV__ && enableDebugTracing) logRenderStopped();
 if (enableSchedulingProfiler) markRenderStopped();

 // 重置全局状态
 workInProgressRoot = null;
 workInProgressRootRenderLanes = NoLanes;

 return workInProgressRootExitStatus;  // 返回渲染结果状态
}

workLoopSync函数实现,与异步相比,少了shouldYield的判断

function workLoopSync() {
 // Already timed out, so perform work without checking if we need to yield.
 while (workInProgress !== null) {
   performUnitOfWork(workInProgress);
 }
}