React-Reconciler 是用于调度react应用的一个模块,负责把react的调和/渲染阶段使用Scheduler包来调度

React的优先级

react 的优先级是react内部使用的优先级,是独立与Scheduler包中的优先级,在react内部有两个方法用于和Scheduler的优先级置换

react中的优先级:

  1. export const ImmediatePriority: ReactPriorityLevel = 99;
  2. export const UserBlockingPriority: ReactPriorityLevel = 98;
  3. export const NormalPriority: ReactPriorityLevel = 97;
  4. export const LowPriority: ReactPriorityLevel = 96;
  5. export const IdlePriority: ReactPriorityLevel = 95;
  6. // NoPriority is the absence of priority. Also React-only.
  7. export const NoPriority: ReactPriorityLevel = 90;
  1. // react的优先级转换为Scheduler的优先级
  2. function reactPriorityToSchedulerPriority(reactPriorityLevel) {
  3. switch (reactPriorityLevel) {
  4. case ImmediatePriority:
  5. return Scheduler_ImmediatePriority;
  6. case UserBlockingPriority:
  7. return Scheduler_UserBlockingPriority;
  8. case NormalPriority:
  9. return Scheduler_NormalPriority;
  10. case LowPriority:
  11. return Scheduler_LowPriority;
  12. case IdlePriority:
  13. return Scheduler_IdlePriority;
  14. default:
  15. invariant(false, 'Unknown priority level.');
  16. }
  17. }
  18. // lane的优先级和scheduler的优先级转换
  19. function lanePriorityToSchedulerPriority(
  20. lanePriority: LanePriority,
  21. ): ReactPriorityLevel {
  22. switch (lanePriority) {
  23. case SyncLanePriority:
  24. case SyncBatchedLanePriority:
  25. return ImmediateSchedulerPriority;
  26. case InputDiscreteHydrationLanePriority:
  27. case InputDiscreteLanePriority:
  28. case InputContinuousHydrationLanePriority:
  29. case InputContinuousLanePriority:
  30. return UserBlockingSchedulerPriority;
  31. case DefaultHydrationLanePriority:
  32. case DefaultLanePriority:
  33. case TransitionHydrationPriority:
  34. case TransitionPriority:
  35. case SelectiveHydrationLanePriority:
  36. case RetryLanePriority:
  37. return NormalSchedulerPriority;
  38. case IdleHydrationLanePriority:
  39. case IdleLanePriority:
  40. case OffscreenLanePriority:
  41. return IdleSchedulerPriority;
  42. case NoLanePriority:
  43. return NoSchedulerPriority;
  44. default:
  45. invariant(
  46. false,
  47. 'Invalid update priority: %s. This is a bug in React.',
  48. lanePriority,
  49. );
  50. }
  51. }

Lane

在react中存在多种使用不同的优先级
lane 在react可以满足三种需求:

  1. 可用表示出优先级的不同
  2. 一个lane只能存在一个更新,但是可以被合并,也就是一个lane可以有批的概念
  3. 内部一定大量使用了lane,要方便计算

lane采用31位的二进制数表示,位数越小表示优先级更高

  1. export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
  2. export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
  3. export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
  4. export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
  5. export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
  6. const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;
  7. const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
  8. const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;
  9. export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
  10. export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
  11. const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
  12. const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;
  13. const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
  14. export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;
  15. export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
  16. const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
  17. export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
  18. const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;
  19. export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;

lane采用了31位的二进制数值表示,那么优先级的相关计算就是使用的是位运算

  1. export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
  2. return (a & b) !== NoLanes;
  3. }
  4. export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
  5. return (set & subset) === subset;
  6. }
  7. export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  8. return a | b;
  9. }
  10. export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
  11. return set & ~subset;
  12. }

使用位运算可以很方便的知道一个lane中对于另一个lane的关系

lane中有几个变量同时存在多个1位也就是可以使用多次,也可以包含其他lane。 一个lane被使用时如果有其他的lane包含进来,可以下降的拥有多个位的lane

高优先级打断低优先级任务

优先级高的任务在后产生并开始调度之前会都会通过ensureRootIsScheduled函数来处理

准备本次任务调度协调所需要的lanes和任务优先级,然后判断是否需要调度。 这个函数做的工作:

  1. 首先获取callbackNode也就是上一次调度的任务
  2. 检查任务是否过期,将过期任务放入root.expiredLanes, 这里就是让过期的任务以同步的优先级进行调度
  3. 获取从root。expiredLanes获取renderLanes, 如果是空说明不需要调度return
  4. 获取本次任务,也就是新任务的优先级
  5. 通过判断新任务和旧任务的优先级是否相同,觉得是否发起一次更新
    1. == 说明没有没有,直接复用旧任务。让旧的任务顺带把新产生的任务做了
    2. != 说明新任务的优先级一定高于旧任务,在这里把旧的任务取消
  6. 开启一次新的调度,荣国scheduler包暴漏的schedulerCallback添加一个新的任务

新旧任务的优先级不同时,新任务的优先级一定是高于旧任务的吗? 每次调度去获取任务优先级的时候,都只从root.pendingLanes中获取优先级未被使用的lane, 如果是空的就获取pendingLanes中最右边也就是优先级最高的lane

比如进入页面时,如果useEffect中对一个state改变。 然后手动调用一个dom事件click。 那么click事件的优先级会大于在useEffect中的优先级

  1. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  2. // 当前正在被调度的回调
  3. const existingCallbackNode = root.callbackNode;
  4. // 获取当前应用正在调度的最高优先级的任务lanes
  5. const nextLanes = getNextLanes(
  6. root,
  7. root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  8. );
  9. // 获取新协调任务的优先级
  10. const newCallbackPriority = returnNextLanesPriority();
  11. if (nextLanes === NoLanes) {
  12. // 在react中,如果没有可用的lane,就直接返回
  13. if (existingCallbackNode !== null) {
  14. cancelCallback(existingCallbackNode);
  15. root.callbackNode = null;
  16. root.callbackPriority = NoLanePriority;
  17. }
  18. return;
  19. }
  20. // 如果已经存在被调度的任务, 是否打断
  21. if (existingCallbackNode !== null) {
  22. const existingCallbackPriority = root.callbackPriority;
  23. // 优先级相同的情况下不会发起新的调度
  24. if (existingCallbackPriority === newCallbackPriority) {
  25. return;
  26. }
  27. // 取消低优先级任务,重新开始调度一次高优先级的任务
  28. // 也就是高优先级任务打断低优先级任务,实现基于优先级的任务插队
  29. cancelCallback(existingCallbackNode);
  30. }
  31. // Schedule a new callback.
  32. let newCallbackNode;
  33. /*
  34. 在调用函数之前会将react内部的优先级转换为scheduler包中的优先级
  35. 这里会调用schedulerCallback 发起一次新的调度任务
  36. 回调在异步模式下是performConcurrentWorkOnRoot,同步为performSyncWorkOnRoot
  37. schedulerCallback会返回新创建的task
  38. var newTask = {
  39. id: taskIdCounter++, // 任务数
  40. callback, // 传入的回调函数,用于本次task的处理程序
  41. priorityLevel, // 任务的优先级
  42. startTime, // 开始时间
  43. expirationTime, // 过期时间
  44. sortIndex: -1, // 优先队列的排序依据
  45. };
  46. */
  47. // 为根节点添加正在调度的任务
  48. root.callbackPriority = newCallbackPriority; // 调度任务的优先级属于react内部的
  49. root.callbackNode = newCallbackNode; // newTask
  50. }

如果一个新的任务的优先级比正在执行的任务优先级高,会取消调上次的任务,重新调度一次新的任务

异步可中断

同步模式为render创建的应用会调用workLoopSync, 异步模式为createRoot 创建的应用调用workLoopConcurrent 二者的区别就是异步可以暂定函数的执行,由shouldYield决定

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function workLoopConcurrent() {
  // 异步模式, 可终端
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

// shouldYield 实现
 var shouldYieldToHost = function() {
   // getCurrentTime 函数为获取当前的performance.now函数返回的时间戳
   // daedline 是剩余时间
  return getCurrentTime() >= deadline;
};

// 中断任务并不会调用 prepareFreshStack函数重置

中断之后的任务会继续放在taskQueue中

中断可恢复

react Concurrent模式为异步模式,是由createRoot方法创建的根节点应用,ReactDom.render创建的应用为同步模式,二者的区别就是异步模式可被打断

// 在异步模式的的入口调度函数内,在当前正在使用的回调和根节点的回调相同时会返回一个函数
// 由scheduler 托管
function performConcurrentWorkOnRoot(root) {
    // ... 略

  // callbackNode 为调度任务的callback属性,也就是现在正在处于调度阶段
  // originalCallbackNode 是当前正在执行的callback
  if (root.callbackNode === originalCallbackNode) {   
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}

// scheduler包调度开始执行任务的函数
function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {

    const callback = currentTask.callback; // 从taskQueue中获取高优先级的任务
    if (typeof callback === 'function') {
      currentTask.callback = null;
     // 在函数内部获取优先级时获取的就是为当前执行回调的优先级,
      currentPriorityLevel = currentTask.priorityLevel;
      // ... 略

      // 执行任务的回调函数
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        // 如果回调函数返回了一个函数,会重新赋值给当前任务的callback属性
        // 此时的回调就是performConcurrentWorkOnRoot
        currentTask.callback = continuationCallback;
      } else {
           // 如果不是,并且当前任务执行完毕就从队列中删除
        // 或者前后两个任务不同, 比如高优先级任务打断低优先级任务
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
    } else {
      pop(taskQueue);
    }
    currentTask = peek(taskQueue);
  }
  // 如果taskQueue中任务为空,就从timerQueue中获取最新过期的任务,并等待超时时间
  if (currentTask !== null) {
    return true;
  } else {
    const firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
  }
}

饥饿问题

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {

  // 检查过期任务,如果存在,立即放到root.expiredLanes,
  // 接下来的将这个任务以同步模式立即执行
  markStarvedLanesAsExpired(root, currentTime);

  // ...

}

已经过期的低优先级任务在下次新任务开始时,会优先被处理

react让低优先级任务过期的方式,变成同步任务解决饥饿问题