React18代码的探索(三)

177 阅读11分钟

React18代码的探索(二)

调度根节点 ensureRootIsScheduled

ensureRootIsScheduled函数在这里的作用是确保根节点被正确调度,处理待处理的更新

调度部分分为同步和异步两种情况。如果是同步优先级(SyncLane),则根据根节点类型(LegacyRoot或ConcurrentRoot)安排同步任务,可能使用微任务或立即调度。对于异步情况,根据车道转换为调度器优先级,调用scheduleCallback安排并发任务。

更新触发 → ensureRootIsScheduled → 任务调度 → 执行渲染
                   ↓
   协调同步/异步任务、处理优先级冲突、优化微任务调度
graph TD
    A[开始] --> B{存在过期车道?}
    B --> C[标记过期车道]
    C --> D[获取nextLanes]
    D --> E{有任务?}
    E -->|否| F[取消调度]
    E -->|是| G[计算新优先级]
    G --> H{优先级变化?}
    H -->|否| I[复用任务]
    H -->|是| J[取消旧任务]
    J --> K{同步优先级?}
    K -->|是| L[同步调度]
    K -->|否| M[异步调度]
    L --> N[微任务优化]
    M --> O[优先级转换]
    N & O --> P[更新根状态]
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  // 1. 检查是否存在现有任务
  const existingCallbackNode = root.callbackNode;
  // 标记过期的Lanes 饥饿更新检测
  markStarvedLanesAsExpired(root, currentTime);
  // 2. 计算下一个需要处理的Lanes
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );

  // 3. 如果没有需要处理的Lanes
  if (nextLanes === NoLanes) {
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode); // 取消现有调度
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  // 4. 计算新任务的优先级
  const newCallbackPriority = getHighestPriorityLane(nextLanes);

  // 5. 处理现有任务与新任务的关系
  if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      return; // 相同优先级无需重新调度
    }
    cancelCallback(existingCallbackNode); // 取消旧的低优先级任务
  }

  // 6. 创建新的调度任务
  let newCallbackNode;
  if (newCallbackPriority === SyncLane) {
     // 同步任务处理
     if (root.tag === LegacyRoot) {
       scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    // 微任务优化处理
    scheduleMicrotask(() => {
      if (执行环境安全) flushSyncCallbacks();
    });
  } else { 
    // 异步任务调度
    let schedulerPriorityLevel;
    //优先级映射
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:        // 离散事件(如点击/键盘)
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:      // 连续事件(如滚动)
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:          // 默认优先级
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:             // 空闲优先级
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:                            // 未知优先级
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    // 调度并发任务
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  // 7. 更新根节点的调度信息
  root.callbackNode = newCallbackNode;
  root.callbackPriority = newCallbackPriority;
}

1.markStarvedLanesAsExpired

标记过期的Lanes 饥饿更新检测

  • 遍历所有待处理通道(pendingLanes)
  • 对未设置过期时间的活跃通道(非挂起/已唤醒)计算新过期时间
  • 对已超时的通道标记为过期(expiredLanes)

设计目的 :

  • 防止高优先级更新被低优先级更新无限期阻塞
  • 确保长时间未处理的更新最终能被强制执行
  • 平衡用户交互响应与后台任务处理
//不同通道类型的超时设置
SyncLane → 250ms
TransitionLane → 5000ms 
RetryLaneNoTimestamp(永不过期)

function markStarvedLanesAsExpired(
  root: FiberRoot,
  currentTime: number,
): void {
  // 1. 获取待处理通道
  const pendingLanes = root.pendingLanes;
  const suspendedLanes = root.suspendedLanes;
  const pingedLanes = root.pingedLanes;
  const expirationTimes = root.expirationTimes;

  // 2. 遍历所有待处理通道
  let lanes = pendingLanes;
  while (lanes > 0) {
    // 3. 选取任意通道索引
    const index = pickArbitraryLaneIndex(lanes);
    const lane = 1 << index;

    const expirationTime = expirationTimes[index];
    
    // 4. 处理未设置过期时间的通道
    if (expirationTime === NoTimestamp) {
      if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) {
        // 5. 计算新过期时间(当前时间 + 通道对应超时值)
        expirationTimes[index] = computeExpirationTime(lane, currentTime);
      }
    } else if (expirationTime <= currentTime) {
      // 6. 标记过期通道
      root.expiredLanes |= lane;
    }

    // 7. 移出已处理通道
    lanes &= ~lane;
  }
}

流程图

graph TD
    A[scheduleUpdateOnFiber] --> B[markRootUpdated]
    B --> C{是否过期?}
    C -->|Yes| D[加入expiredLanes]
    C -->|No| E[继续等待]
    D --> F[getNextLanes]

2.getNextLanes

计算下一个需要处理的Lanes

function getNextLanes(
  root: FiberRoot,    // 当前Fiber根节点
  wipLanes: Lanes,    // 工作中的Lanes
) {
  const pendingLanes = root.pendingLanes;
  if (pendingLanes === NoLanes) {
    //pendingLanes是否为NoLanes,如果是则返回NoLanes
    return NoLanes;
  }
  //处理非空闲和空闲任务的不同情况,确保高优先级任务优先执行
  //优先处理非空闲任务(NonIdleLanes)
  const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes; // 过滤未挂起任务
  // 处理任务饥饿问题(expiredLanes)
  //... existing code...
  //中断策略
  if (nextLane >= wipLane) {
    return wipLanes; // 保持现有任务
  } else {
    return nextLanes; // 执行更高优任务
  }
  // ... existing code...
  const entangledLanes = root.entangledLanes;
  if (entangledLanes !== NoLanes) {
    const entanglements = root.entanglements;
    let lanes = nextLanes & entangledLanes;
    while (lanes > 0) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
      // 通道纠缠机制
      // 当多个更新来自同一事件源时(如连续输入),强制合并到同一批次处理
      // 确保同一事件源的更新在同一时间窗口内被合并处理
      nextLanes |= entanglements[index];

      lanes &= ~lane;
    }
  }

  return nextLanes;

}
  • 示例:

const entanglements = [0b1000, 0b0010]; // 通道0与3纠缠,通道1与1纠缠

let nextLanes = 0b0001; // 初始通道

nextLanes |= entanglements[0]; // 0b0001 | 0b1000 = 0b1001

流程图

graph TD
    A[事件触发setState] --> B{是否连续事件?}
    B -->|Yes| C[标记为纠缠通道]
    B -->|No| D[独立通道处理]
    C --> E[合并到同一批次]
    D --> F[按优先级调度]

3.scheduleCallback 调度系统的核心实现

实际调用的是unstable_scheduleCallback

unstable_scheduleCallback 函数是调度系统的核心实现,主要负责任务调度和时间切片管理

graph TD
 A[入口参数] --> B[计算起始时间]
 B --> C{判断优先级}
 C -->|Immediate| D[立即超时-1ms]
 C -->|UserBlocking| E[250ms超时]
 C -->|Normal| F[5000ms超时]
 C -->|Low| G[10000ms超时]
 C -->|Idle| H[最大超时]
 D --> I[生成任务对象]
 E --> I
 F --> I
 G --> I
 H --> I
 I --> J{是否延迟任务?}
 J -->|是| K[加入定时器队列]
 J -->|否| L[加入任务队列]
 K --> M[设置定时器]
 L --> N[请求调度回调]

函数实现

function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }

  var timeout;
  //优先级时间映射
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1(立即执行)
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250ms
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT; // 1073741823ms(永不超时)
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT; // 10000ms 
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT; // 5000ms
      break;
  }

  var expirationTime = startTime + timeout;

  var newTask = {
      id: taskIdCounter++,       // 唯一标识
      callback,                  // 待执行回调
      priorityLevel,             // 优先级等级
      startTime,                 // 计划执行时间
      expirationTime,            // 过期时间
      sortIndex: -1             // 堆排序索引
    };
  if (enableProfiling) {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {
    //延迟任务
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);  // 加入延迟队列
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      if (isHostTimeoutScheduled) {
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // 设置主机定时器(通过 MessageChannel 实现)
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    // 加入主队列
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }

  return newTask;
}

function peek(heap: Heap): Node | null {
  return heap.length === 0 ? null : heap[0];
}

这里使用了两个独立的最小堆( taskQueue 和 timerQueue )分别管理立即任务和延迟任务

定时队列(timerQueue):按 startTime 排序的最小堆

主任务队列(taskQueue):按 expirationTime 排序的最小堆

1.handleTimeout

被requestHostTimeout函数调用的时间处理函数

主要职责是处理定时器队列中的任务,将它们转移到任务队列中,以便调度执行

// 核心定时器处理函数
function handleTimeout(currentTime) {
// 1. 取消已安排的hostTimeout
isHostTimeoutScheduled = false;

// 2. 推进定时器检查(将到期的定时任务移入主队列)
advanceTimers(currentTime);

// 3. 任务队列调度逻辑
if (!isHostCallbackScheduled) {
 if (peek(taskQueue) !== null) { // 主任务队列有任务
   isHostCallbackScheduled = true;
   requestHostCallback(flushWork); // 请求主线程回调
 } else { // 主队列空则检查定时队列
   const firstTimer = peek(timerQueue);
   if (firstTimer !== null) {
     // 4. 计算下次触发时间(最近定时任务的开始时间)
     requestHostTimeout(
       handleTimeout, 
       firstTimer.startTime - currentTime
     );
   }
 }
}
}

advanceTimers检查定时器队列(timerQueue)中的任务,将那些已经到期的任务转移到主任务队列(taskQueue)中。这有助于调度器及时处理延迟任务

function advanceTimers(currentTime) {
// 1. 获取定时队列头部任务
let timer = peek(timerQueue);

// 2. 循环处理定时任务
while (timer !== null) {
 if (timer.callback === null) {
   // 3. 任务已取消:移除空回调任务
   pop(timerQueue);
 } else if (timer.startTime <= currentTime) {
   // 4. 任务到期:转移至主队列
   pop(timerQueue);
   //该操作将延迟任务转换为立即任务,优先级由其过期时间决定
   timer.sortIndex = timer.expirationTime; 
   push(taskQueue, timer);
   
   // 5. 性能分析标记
   if (enableProfiling) {
      // 记录任务激活时间
     markTaskStart(timer, currentTime);
     // 标记任务进入主队列
     timer.isQueued = true;
   }
 } else {
   // 6. 未到期任务:提前退出循环(定时队列是最小堆)
   return;
 }
 // 7. 获取下一个待处理任务
 timer = peek(timerQueue);
}
}

流程图

graph TD
A[定时队列检测] --> B[移除空回调任务] 
B -->C[转移到期任务]  
C -->D[更新队列排序]
D -->E[性能标记]

2.flushWork

flushWork函数接收hasTimeRemaining和initialTime两个参数

该函数作为任务执行流程的入口点,负责连接调度器与浏览器事件循环(通过 requestHostCallback

function flushWork(hasTimeRemaining, initialTime) {
// 性能分析标记:调度器恢复运行
if (enableProfiling) {
// 记录恢复时间点
 markSchedulerUnsuspended(initialTime);
}

// 重置调度状态
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
 // 取消不必要的定时器
 isHostTimeoutScheduled = false;
 cancelHostTimeout();
}

// 设置工作状态
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;

try {
 // 核心工作循环
 if (enableProfiling) {
   try {
     return workLoop(hasTimeRemaining, initialTime);
   } catch (error) {
     // 错误处理:标记错误任务
     if (currentTask !== null) {
       const currentTime = getCurrentTime();
       markTaskErrored(currentTask, currentTime);
       currentTask.isQueued = false;
     }
     throw error;
   }
 } else {
   // 生产环境直接执行工作循环
   return workLoop(hasTimeRemaining, initialTime);
 }
} finally {
 // 状态恢复逻辑
 currentTask = null;
 currentPriorityLevel = previousPriorityLevel;
 isPerformingWork = false;
 
 // 性能分析标记:调度器挂起
 if (enableProfiling) {
   const currentTime = getCurrentTime();
   markSchedulerSuspended(currentTime);
 }
}
}

流程图

graph LR
A[调度开始] --> B[状态重置]
B --> C[执行工作循环]
C --> D{是否异常?}
D -->|是| E[错误处理]
D -->|否| F[正常返回]
E --> F
F --> G[状态恢复]
G --> H[性能标记]

3.workLoop

这个函数是React调度器的核心部分,负责处理任务队列中的任务

函数接收两个参数:hasTimeRemaininginitialTime,分别表示是否有剩余时间和初始时间。函数内部首先调用advanceTimers来将定时器队列中到期的任务转移到主任务队列,然后进入一个循环处理主队列中的任务

循环条件检查当前任务是否存在且调度器未被暂停。在循环内部,首先检查当前任务的过期时间是否大于当前时间,并且是否需要让出主线程(通过shouldYieldToHost判断)。如果满足条件,则中断循环,暂停执行

然后,函数获取当前任务的回调函数,如果回调存在,则执行它。回调可能返回一个延续函数(continuationCallback),如果有的话,将其赋值给当前任务的回调,否则标记任务完成并从队列中移除。每次循环结束后,都会再次调用advanceTimers来处理可能新到期的定时任务。

最后,函数返回是否有剩余工作,如果没有则安排下一次定时器检查。

// ... existing code ...
function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
     // 查当前任务的过期时间是否大于当前时间
     // 判断是否需要让出主线程
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      break;
    }
    const callback = currentTask.callback;
    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      // ... 性能分析标记 ...
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        // ... 性能分析标记 ...
      } else {
        // ... 任务完成处理 ...
      }
      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }
    currentTask = peek(taskQueue);
  }
  // ... 后续处理 ...
}
// ... existing code ...

流程图

graph TD
   A[开始] --> B[推进定时器]
   B --> C{任务队列非空?}
   C -->|是| D[检查任务过期时间]
   D --> E{需要让出主线程?}
   E -->|是| F[中断执行]
   E -->|否| G[执行任务回调]
   G --> H{有延续回调?}
   H -->|是| I[保存回调]
   H -->|否| J[移除任务]
   J --> K[推进定时器]
   K --> C
   C -->|否| L[安排下次检查]
时间切片控制 shouldYieldToHost

这个函数的主要作用是决定是否让出主线程给浏览器执行更高优先级的任务,比如用户输入或渲染。这是React实现时间切片和并发模式的核心机制之一

时间切片允许React将任务分成小块,避免阻塞主线程,而该函数就是决定何时中断任务的关键

函数开始计算已经过去的时间timeElapsed,如果小于frameInterval,则不需要让出。这确保了短时间的任务不会频繁中断。然后是多个条件判断,根据不同的时间间隔和是否有挂起的输入来决定是否让出


export const enableSchedulerDebugging = false;
export const enableIsInputPending = false;
export const enableProfiling = false;
export const enableIsInputPendingContinuous = false;
export const frameYieldMs = 5; // 单帧时间阈值(可配置)
export const continuousYieldMs = 50; // 持续输入检测阈值 
export const maxYieldMs = 300; // 最大执行时间阈值

function shouldYieldToHost() {
  // 计算当前帧已用时间(单位:毫秒)
  const timeElapsed = getCurrentTime() - startTime;
  
  // 第一阶段判断:短时间任务不中断
  if (timeElapsed < frameInterval) { // frameInterval默认5ms
    return false; // 未超过单帧时间,继续执行
  }

  // 第二阶段判断:长时间任务中断策略
  if (enableIsInputPending) { // 是否启用输入检测
    if (needsPaint) { // 需要渲染(通过requestPaint标记)
      return true; // 立即让出主线程
    }
    
    // 根据阻塞时长分级处理
    if (timeElapsed < continuousInputInterval) { // 默认150ms
      // 中等时长:只检测离散输入(点击/按键)
      return isInputPending?.(); // 使用浏览器原生API检测
    } 
    else if (timeElapsed < maxInterval) { // 默认300ms
      // 较长时间:检测所有输入(含持续输入)
      return isInputPending?.(continuousOpti ons); 
    }
    else { // 超过最大阈值
      return true; // 强制让出
    }
  }
  
  // 兜底逻辑:无输入检测能力时默认让出
  return true;
}
  • 5ms时间切片保证基础响应性
  • 150ms阈值优化用户体验(允许中等耗时任务)
  • 300ms强制让出避免长时间阻塞

渲染请求处理

  • 与浏览器渲染管线协同
  • 确保JS执行不阻塞UI更新
requestPaint() // 标记需要渲染
needsPaint = true // 触发渲染让出逻辑

动态阈值调整

  • 开发者可手动设置帧率
  • 自动计算frameInterval = 1000 / fps
forceFrameRate(fps) // 动态调整frameInterval

流程图

graph TD
   A[开始] --> B{时间小于5ms?}
   B -->|是| C[继续执行] 
   B -->|否| D{需要渲染?}
   D -->|是| E[让出主线程]
   D -->|否| F{时间小于150ms?}
   F -->|是| G[检测离散输入]
   F -->|否| H{时间小于300ms?}
   H -->|是| I[检测所有输入]
   H -->|否| J[强制让出]
   G --> K{有输入?}
   I --> K
   K -->|是| E
   K -->|否| C

4.requestHostCallback

将传入的callback赋值给scheduledHostCallback,然后检查isMessageLoopRunning是否为false,如果是,就设置为true并调用schedulePerformWorkUntilDeadline()

该函数在React调度系统中的关键作用: ⚡ 连接调度器核心与宿主环境事件循环 ⚡ 实现浏览器环境下5ms时间切片的基础 ⚡ 支撑React并发模式的非阻塞渲染 ⚡ 协调同步任务与异步任务的执行切换

schedulePerformWorkUntilDeadline是跨环境调度适配的核心

  • 浏览器环境 :使用 MessageChannel 的 postMessage
  • Node环境 :使用 setImmediate
  • 降级方案 :使用 setTimeout(0)

requestHostCallback(flushWork)

function requestHostCallback(callback) {
// 1. 缓存当前调度任务
scheduledHostCallback = callback;

// 2. 检查消息循环状态
if (!isMessageLoopRunning) {
 // 3. 激活消息循环
 isMessageLoopRunning = true;
 // 4. 调度任务执行(浏览器/Node环境适配)
 schedulePerformWorkUntilDeadline();
}

messageChannel下时间切片管理

  • 使用 MessageChannel 实现零延迟的任务调度
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
 // 触发下一次任务调度
 port.postMessage(null);
};

performWorkUntilDeadline 与workLoop 形成协作关系。

该函数的主要作用是执行任务循环,检查当前是否有剩余时间执行任务,执行任务队列中的任务,如果任务未完成,安排下一次调度

const performWorkUntilDeadline = () => {
// ... existing code ...
let currentTime = getCurrentTime();
const hasTimeRemaining = true;

let hasMoreWork = workLoop(hasTimeRemaining, currentTime);

if (hasMoreWork) {
 schedulePerformWorkUntilDeadline();
} else {
 // ... existing code ...
}
};

React18代码的探索(四)