React18代码的探索(二)

379 阅读11分钟

React18代码的探索(一)

当我们执行完createRoot后,调用render展示组件,从updateContainer开始

updateContainer

graph TD
    A[updateContainer入口] --> B[获取当前FiberRoot实例]
    B --> C[获取事件时间requestEventTime]
    C --> D[请求更新Lane通道requestUpdateLane]
    D --> E[获取子树上下文getContextForSubtree]
    E --> F[创建更新对象createUpdate]
    F --> G["设置payload {element: ReactElement}"]
    G --> H[处理回调函数]
    H --> I[将更新加入队列enqueueUpdate]
    I --> J[调度更新scheduleUpdateOnFiber]
    J --> K[进入reconciler协调阶段]
    
    style A fill:#f9f,stroke:#333
    style K fill:#bbf,stroke:#333
updateContainer(element, container, parentComponent, callback){
   // 获取FiberNode对象,这个对象代表了当前组件树的根节点
  const current = container.current;
  // 获取当前事件时间戳,距离系统启动时间
  const eventTime = requestEventTime();
  // 获取更新优先级
   const lane = requestUpdateLane(current);
  // 获取子树的上下文
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    //初次渲染时设置容器上下文 上下文为空
    container.context = context;
  } else {
  // 非首次渲染时将新上下文存入pendingContext,当组件下次更新时,pendingContext会被赋值给context
    container.pendingContext = context;
  }
  //创建一个更新对象
  const update = createUpdate(eventTime, lane);
  // 负责将新的更新添加到Fiber节点的更新队列中
  const root = enqueueUpdate(current, update, lane);
   if (root !== null) {
    // 调度更新
    scheduleUpdateOnFiber(root, current, lane, eventTime);
    entangleTransitions(root, current, lane);
  }
  return lane;
}

1.requestEventTime

  1. 时间原点机制
    • 返回的是以应用启动时间为原点的相对时间戳(使用 performance.now() 实现),用于精确计算事件触发到实际处理的延迟时间
  2. 事件时间戳与当前渲染周期的 startTime 比较
    • 事件时间戳与当前渲染周期的 startTime 比较
    • 时间差越大(越早触发的更新),优先级越高
    • 时间差越小(越晚触发的更新),优先级越低
    • 时间差为 0 (同一时间触发的更新),优先级相同
  3. 批量更新优化
    • 同一事件周期内的多个更新会合并为单个调度
    • 事件时间戳作为合并的标识符(同一时间窗口内的更新共享一个批次)
    • 事件时间戳相同的更新会被合并到同一个批次中
    • 事件时间戳不同的更新会被独立调度

executionContext:代表渲染期间的上下文

RenderContext:表示当前是否处于渲染阶段。

CommitContext:代表渲染期间的执行上下文,是提交更新的阶段,这个阶段发生在React的渲染周期中,用于处理组件更新后的最终提交操作

graph TD
    A[开始] --> B{是否在React上下文并且处于RenderContext或者 CommitContext}
    B -->|是| C[返回now]
    B -->|否| D{currentEventTime是否为NoTimestamp}
    D -->|否| E[返回currentEventTime]
    D -->|是| F[设置currentEventTime=now]
    F --> G[返回currentEventTime]
export function requestEventTime() {
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // 处于react上下文,存在正在render和commit的事件 正处于render和commit阶段
    // 返回当前时间
    // We're inside React, so it's fine to read the actual time.
    return now();
  }

    //不再react上下文,可能存在浏览器事件中
  // We're not inside React, so we may be in the middle of a browser event.
  if (currentEventTime !== NoTimestamp) {
    // 可能是多次同步执行更新,知道下一次更新,均使用相同时间
    // 返回当前事件时间
    // Use the same start time for all updates until we enter React again.
    return currentEventTime;
  }
  
 // 第一次更新的时候
 // 之前的任务已经执行完,开启新的任务时候需要重新计算时间
  // This is the first update since React yielded. Compute a new start time.
  currentEventTime = now();
  return currentEventTime;
}

2.requestUpdateLane

计算更新车道函数。React中有一个Lane的概念,意为车道,具有不同的优先级,通过通过一个32位的二进制数来表示,数值越小表示优先级越高。 同的Lane代表不同的任务,最终转换为调度优先级。

常见的lane有
type Lane = number;
const SyncLane = 0b0000000000000000000000000000001; // 最高优先级
const InputContinuousLane = 0b0000000000000000000000000000100;
const DefaultLane = 0b0000000000000000000000000010000; 
const currentEventTransitionLane = NoLanes = 0b0000000000000000000000000000000
...

该函数实现了 React 的优先级调度策略,通过车道(Lane)模型实现:

  • 高优先级更新可中断低优先级工作
  • 批量更新合并(相同车道更新合并)
  • 饥饿问题预防(长时间未处理的车道会提升优先级)
  • 动态优先级调整(根据事件类型动态分配优先级)
  • 过渡更新(Transition)处理(在过渡期间的更新)
  • 同步模式处理(立即执行)
  • 异步模式处理(根据优先级调度)
function requestUpdateLane(fiber: Fiber): Lane {
  if ((fiber.mode & ConcurrentMode) === NoMode) {
  // 同步模式处理
  // 当 Fiber 树处于同步模式(ConcurrentMode 关闭)时,直接返回最高优先级的同步车道(SyncLane),确保立即执行
    return (SyncLane: Lane);
  }else if (
    !deferRenderPhaseUpdateToNextBatch &&
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
      // render 阶段 
      //返回最高优先级车道
      return pickArbitraryLane(workInProgressRootRenderLanes);
  }


  // 过渡(Transition)更新处理
  const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
    if (currentEventTransitionLane === NoLane) {
       //不存在过度更新车道,为多个并发过渡更新间循环分配独立车道
      currentEventTransitionLane = claimNextTransitionLane();
    }
    // 当存在过渡更新(Transition)时,返回当前事件的过渡车道
    return currentEventTransitionLane;
  }

  // 当存在当前更新优先级(由 flushSync 等 API 提供)时,返回该优先级的车道
  const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if (updateLane !== NoLane) {
    return updateLane;
  }

  // 通过 getCurrentEventPriority,根据宿主环境的事件类型(如鼠标点击、键盘输入等),分配对应的优先级车道(InputContinuousLane、DefaultLane 等)
  const eventLane: Lane = (getCurrentEventPriority(): any);
  return eventLane;
}

3.getContextForSubtree

获取子树的上下文

graph TD
    A[调用 getContextForSubtree] --> B{parentComponent 存在?}
    B -- 否 --> C[返回 emptyContextObject]
    B -- 是 --> D[获取Fiber节点]
    D --> E[获取当前未屏蔽Context]
    E --> F{Fiber类型为ClassComponent?}
    F -- 否 --> G[返回父级Context]
    F -- 是 --> H{是否为旧版Context提供者?}
    H -- 否 --> G
    H -- 是 --> I[处理子Context继承]
    I --> J[返回处理后的Context]
function getContextForSubtree(
  parentComponent: ?React$Component<any, any>,
): Object {
    // 如果父级为空,则返回空上下文
  if (!parentComponent) {
    return emptyContextObject;
  }
    
   // 获取父组件的实例fiber
  const fiber = getInstance(parentComponent);
  // 获取当前未屏蔽的上下文
  const parentContext = findCurrentUnmaskedContext(fiber);
    
    //如果父组件是类组件
  if (fiber.tag === ClassComponent) {
    const Component = fiber.type;
    //判断是否为旧版Context提供者
    if (isLegacyContextProvider(Component)) {
    //子组件继承
      return processChildContext(fiber, Component, parentContext);
    }
  }

  return parentContext;
}

4.createUpdate

//一个更新对象,用于描述一个更新操作,包含了更新的内容、优先级、回调函数等信息

  • 以下为对象结构
const update: Update<*> = {
  eventTime,       // 事件时间戳(来自performance.now())
  lane,            // 更新所属的优先级通道
  tag: UpdateState, // 更新类型标识(0=普通状态更新)
  payload: null,    // 实际更新内容(可以是对象或函数)
  callback: null,   // 更新完成后的回调函数
  next: null,       // 指向下一个更新对象的指针,构成循环链表的关键指针
};

其中tagd的种类如下

  • UpdateState =0 ==> 普通状态更新
  • ReplaceState = 1 ==> 替换整个状态
  • ForceUpdate = 2 ==> 强制重新渲染
  • CaptureUpdate =3 ==> 错误边界捕获的更新

5.enqueueUpdate

我们获取完事件时间、车道级别、子树上下文之后,创建了更新对象,需要通过enqueueUpdate插入更新队列函数将其插入更新队列。

该函数接收一个fiber、update对象和lane(优先级通道),然后将update加入到共享队列中,根据isUnsafeClassRenderPhaseUpdate 标志来判断更新是否在类组件的渲染阶段,来决定不同的管理和调度

export function enqueueUpdate<State>(
  fiber: Fiber,
  update: Update<State>,
  lane: Lane,
): FiberRoot | null {
  // 获取当前fiber的更新队列
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
   // 已卸载的fiber直接返回
    return null; 
  }

  // 获取共享队列(current与WIP共享)
  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;

  // 类组件渲染阶段处理 并不安全 比如render阶段setState
  // 类组件的更新队列是一个环形链表,每个更新对象都有一个 next 指针指向下一个更新对象
  if (isUnsafeClassRenderPhaseUpdate(fiber)) {
    // 直接操作循环链表
    const pending = sharedQueue.pending;
    if (pending === null) {
      update.next = update; // 创建自引用循环链表
    } else {
      update.next = pending.next;
      pending.next = update;
    }
    sharedQueue.pending = update; // 更新队列头指针

    // 优先级标记
    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
    // 并发组件渲染模式
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}

6.enqueueConcurrentClassUpdate

enqueueConcurrentClassUpdate实际调用的是enqueueUpdate,主要功能是将更新暂存到全局队列中以便后续批量处理,保证渲染过程可中断

触发更新 → enqueueUpdate(暂存) → finishQueueingConcurrentUpdates(批量处理) → 调度更新 → 渲染

function enqueueConcurrentClassUpdate<State>(
  fiber: Fiber,
  queue: ClassQueue<State>,
  update: ClassUpdate<State>,
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
  return getRootForUpdatedFiber(fiber);
}

function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
    //concurrentQueues 是一个全局的扁平化数组 ,按顺序存储更新的相关信息:
    //每个更新占用数组中的 4 个连续位置: [fiber, queue, update, lane] 比如 fiber、队列、更新级别、车道
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;
 // 合并更新通道(优先级处理)
  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
  // 更新 fiber 的 lanes 字段
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  //同步备用节点(alternate)的 lanes
  const alternate = fiber.alternate;
  //双树协调,同时更新当前 fiber 和 alternate fiber 的 lanes 字段,保持双 fiber 树状态同步
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}

1.双树结构 React 中的 Fiber 树是一个双树结构,每个 Fiber 节点都有两个指针:return 和 child。

  • return 指向父节点,用于构建树状结构
  • child 指向子节点,用于遍历子节点

2.双树同步 当一个更新触发时,React 会同时更新当前 Fiber 树和备用 Fiber 树(alternate)。

  • 根节点的 current 指向当前 Fiber 树
  • 根节点的 alternate 指向备用 Fiber 树

3.优先级标记 每个 Fiber 节点都有一个 lanes 属性,用于记录当前节点的更新优先级。

  • 当一个更新触发时,会将更新的优先级标记到当前 Fiber 节点和备用 Fiber 节点的 lanes 中。
  • 这样,在渲染过程中,React 可以根据 lanes 来决定是否中断或跳过某些节点的渲染。

mergeLanes 是一个位或操作合并车道方法,按位或运算在这里的作用是将两个优先级集合合并,形成一个包含两者所有优先级的新集合。例如,如果a0b0001(SyncLane),b0b0010(InputContinuousLane),合并后的结果就是0b0011,表示同时包含这两个优先级。

该函数是 React 并发模式(Concurrent Mode)实现细粒度调度的基础,通过位掩码操作高效管理复杂优先级关系。 组件多次 setState 的优先级合并 等会用到此方法

function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}

getRootForUpdatedFiber这个函数的作用是当发生状态更新(如setState)时,确保根节点(root)被正确调度,通过遍历fiber树的返回路径来找到根节点

  • 确保更新能够正确冒泡到根节点
  • 为调度器提供更新入口点
  • 防止在未完成挂载的组件上执行无效更新
  • 维护 fiber 树结构的完整性
graph TD
    A[sourceFiber] --> B{parent存在?}
    B -->|是| C[检测未挂载状态]
    C --> D[移动至父节点]
    D --> B
    B -->|否| E[检查HostRoot标签]
    E -->|是| F[返回FiberRoot]
    E -->|否| G[返回null]
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
  // 开发环境检测未挂载组件上的更新
  detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
  
  // 通过父节点链向上遍历
  let node = sourceFiber;
  let parent = node.return;
  while (parent !== null) {
    detectUpdateOnUnmountedFiber(sourceFiber, node);
    node = parent;
    parent = node.return;
  }
  
  // 返回 HostRoot 节点的 stateNode(即 FiberRoot)
  return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}

通过 fiber.return 指针逐级向上查找,直到 HostRoot 节点,返回的 FiberRoot 包含整个应用的调度信息

当我们将更新队列创建完并且插入全局后,就要开始通过scheduleUpdateOnFiber调度更新了,他是所有任务更新的唯一入口

7.scheduleUpdateOnFiber 调度更新

这个函数主要负责在Fiber 根节点上调度更新,处理不同的更新场景,比如渲染阶段、被动效果阶段等

在react应用中,调用setStateuseState会触发该函数,父组件重新渲染或者context变化也会触发

最后通过调用ensureRootIsScheduled来安排根节点的任务,并在同步车道且无执行上下文时,立即刷新同步回调

如果处于渲染阶段,则合并车道,否则直接调度更新根节点

用户操作/状态变更 → scheduleUpdateOnFiber → 更新调度 → 渲染流程
                        ↓
      协调同步/异步更新、处理不同优先级任务
graph TD
    A[开始] --> B{渲染阶段?}
    B -->|是| C[记录渲染阶段更新]
    B -->|否| D[处理正常更新]
    D --> E[开发工具追踪]
    E --> F[性能分析回调]
    F --> G[过渡追踪处理]
    G --> H[合并交错更新]
    H --> I[调度更新任务]
    I --> J{同步模式?}
    J -->|是| K[立即执行回调]
    J -->|否| L[结束]
export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  // 1. 嵌套更新检查(防止无限循环)
  // NESTED_UPDATE_LIMIT 默认50次
  checkForNestedUpdates();

  // 开发模式错误检测
  if (__DEV__) {
    // 禁止在useInsertionEffect中调度更新
    if (isRunningInsertionEffect) {
      console.error('useInsertionEffect must not schedule updates.');
    }
    // 标记被动效果阶段的更新
    if (isFlushingPassiveEffects) {
      didScheduleUpdateDuringPassiveEffects = true;
    }
  }

  // 2. 标记根节点有待处理更新
  markRootUpdated(root, lane, eventTime);

  // 3. 判断更新类型
  // 通过executionContext 和 RenderContext判断是否处于渲染阶段
  if (
    (executionContext & RenderContext) !== NoLanes &&
    root === workInProgressRoot
  ) {
    / 此更新在渲染阶段被派发。如果更新来源于用户代码(本地Hook更新除外,
    // 这些更新有不同处理方式且不会到达此函数),这属于错误行为。但React
    // 内部某些特性会将其作为实现细节使用,例如选择性水合(selective hydration)。
    
    //比如在组件 render 方法或 Hook 中调用 setState 等更新 API
    warnAboutRenderPhaseUpdatesInDEV(fiber); // 开发警告
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(...); // 合并车道
  } else {
   // 这是常规更新,在渲染阶段之外被调度。例如,在输入事件处理过程中。
   // 常规更新比如,由用户交互或副作用触发的标准状态更新
    if (enableUpdaterTracking) {  
      addFiberToLanesMap(root, fiber, lane);
    }
    
    // Profiler性能追踪
    if (enableProfilerTimer) {  
      // 遍历父节点寻找Profiler组件
      let current = fiber;
      while (current !== null) {
        if (current.tag === Profiler) {
          const {id, onNestedUpdateScheduled} = current.memoizedProps;
          if (typeof onNestedUpdateScheduled === 'function') {
            onNestedUpdateScheduled(id); // 触发回调
          }
        }
        current = current.return;
      }
    }

    // 过渡追踪处理
    if (enableTransitionTracing) {
      const transition = ReactCurrentBatchConfig.transition;
      if (transition !== null) {
        transition.startTime = now(); // 记录开始时间
        addTransitionToLanesMap(root, transition, lane);
      }
    }

    // 4. 处理中间渲染的更新
    if (root === workInProgressRoot) {
      // 合并交错更新车道
      workInProgressRootInterleavedUpdatedLanes = mergeLanes(...);
      
      // 处理挂起状态的根
      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        markRootSuspended(root, workInProgressRootRenderLanes);
      }
    }

    // 5. 调度根节点更新
    ensureRootIsScheduled(root, eventTime);

    // 6. 同步模式优化处理
    if (lane === SyncLane && executionContext === NoContext) {
      resetRenderTimer();
      flushSyncCallbacksOnlyInLegacyMode(); // 立即执行同步回调
    }
  }
}

React18代码的探索(三)