关于react.js:react源码解析15schedulerLane

25次阅读

共计 9753 个字符,预计需要花费 25 分钟才能阅读完成。

react 源码解析 15.scheduler&Lane

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

21.demo

当咱们在相似上面的搜寻框组件进行搜寻时会发现,组件分为搜寻局部和搜寻后果展现列表,咱们冀望输入框能立即响应,后果列表能够有期待的工夫,如果后果列表数据量很大,在进行渲染的时候,咱们又输出了一些文字,因为用户输出事件的优先级是很高的,所以就要进行后果列表的渲染,这就引出了不同工作之间的优先级和调度

Scheduler

咱们晓得如果咱们的利用占用较长的 js 执行工夫,比方超过了设施一帧的工夫,那么设施的绘制就会出不的景象。

Scheduler 次要的性能是工夫切片和调度优先级,react 在比照差别的时候会占用肯定的 js 执行工夫,Scheduler 外部借助 MessageChannel 实现了在浏览器绘制之前指定一个工夫片,如果 react 在指定工夫内没比照完,Scheduler 就会强制交出执行权给浏览器

工夫切片

​ 在浏览器的一帧中 js 的执行工夫如下

​ requestIdleCallback 是在浏览器重绘重排之后,如果还有闲暇就能够执行的机会,所以为了不影响重绘重排,能够在浏览器在 requestIdleCallback 中执行耗性能的计算,然而因为 requestIdleCallback 存在兼容和触发机会不稳固的问题,scheduler 中采纳 MessageChannel 来实现 requestIdleCallback,以后环境不反对 MessageChannel 就采纳 setTimeout。

​ 在之前的介绍中咱们晓得在 performUnitOfWork 之后会执行 render 阶段和 commit 阶段,如果在浏览器的一帧中,cup 的计算还没实现,就会让出 js 执行权给浏览器,这个判断在 workLoopConcurrent 函数中,shouldYield 就是用来判断残余的工夫有没有用尽。在源码中每个工夫片时 5ms,这个值会依据设施的 fps 调整。

function workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {performUnitOfWork(workInProgress);
  }
}
function forceFrameRate(fps) {// 计算工夫片
  if (fps < 0 || fps > 125) {console['error'](
      'forceFrameRate takes a positive int between 0 and 125,' +
        'forcing frame rates higher than 125 fps is not supported',
    );
    return;
  }
  if (fps > 0) {yieldInterval = Math.floor(1000 / fps);
  } else {yieldInterval = 5;// 工夫片默认 5ms}
}

工作的暂停

在 shouldYield 函数中有一段,所以能够晓得,如果以后工夫大于工作开始的工夫 +yieldInterval,就打断了工作的进行。

//deadline = currentTime + yieldInterval,deadline 是在 performWorkUntilDeadline 函数中计算出来的
if (currentTime >= deadline) {
  //...
    return true
}

调度优先级

​ 在 Scheduler 中有两个函数能够创立具备优先级的工作

  • runWithPriority:以一个优先级执行 callback,如果是同步的工作,优先级就是 ImmediateSchedulerPriority

    function unstable_runWithPriority(priorityLevel, eventHandler) {switch (priorityLevel) {// 5 种优先级
        case ImmediatePriority:
        case UserBlockingPriority:
        case NormalPriority:
        case LowPriority:
        case IdlePriority:
          break;
        default:
          priorityLevel = NormalPriority;
      }
    
      var previousPriorityLevel = currentPriorityLevel;// 保留以后的优先级
      currentPriorityLevel = priorityLevel;//priorityLevel 赋值给 currentPriorityLevel
    
      try {return eventHandler();// 回调函数
      } finally {currentPriorityLevel = previousPriorityLevel;// 还原之前的优先级}
    }
  • scheduleCallback:以一个优先级注册 callback,在适当的机会执行,因为波及过期工夫的计算,所以 scheduleCallback 比 runWithPriority 的粒度更细。

    • 在 scheduleCallback 中优先级意味着过期工夫,优先级越高 priorityLevel 就越小,过期工夫离以后工夫就越近,var expirationTime = startTime + timeout;例如 IMMEDIATE_PRIORITY_TIMEOUT=-1,那 var expirationTime = startTime + (-1); 就小于以后工夫了,所以要立刻执行。
    • scheduleCallback 调度的过程用到了小顶堆,所以咱们能够在 O(1)的复杂度找到优先级最高的 task,不理解能够查阅材料,在源码中小顶堆寄存着工作,每次 peek 都能取到离过期工夫最近的 task。
    • scheduleCallback 中,未过期工作 task 寄存在 timerQueue 中,过期工作寄存在 taskQueue 中。

      ​ 新建 newTask 工作之后,判断 newTask 是否过期,没过期就退出 timerQueue 中,如果此时 taskQueue 中还没有过期工作,timerQueue 中离过期工夫最近的 task 正好是 newTask,则设置个定时器,到了过期工夫就退出 taskQueue 中。

      ​ 当 timerQueue 中有工作,就取出最早过期的工作执行。

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 越小
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;//-1
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;//250
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }

  var expirationTime = startTime + timeout;// 优先级越高 过期工夫越小

  var newTask = {// 新建 task
    id: taskIdCounter++,
    callback// 回调函数
    priorityLevel,
    startTime,// 开始工夫
    expirationTime,// 过期工夫
    sortIndex: -1,
  };
  if (enableProfiling) {newTask.isQueued = false;}

  if (startTime > currentTime) {// 没有过期
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);// 退出 timerQueue
    //taskQueue 中还没有过期工作,timerQueue 中离过期工夫最近的 task 正好是 newTask
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {if (isHostTimeoutScheduled) {cancelHostTimeout();
      } else {isHostTimeoutScheduled = true;}
      // 定时器,到了过期工夫就退出 taskQueue 中
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);// 退出 taskQueue
    if (enableProfiling) {markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);// 执行过期的工作
    }
  }

  return newTask;
}

工作暂停之后怎么持续

​ 在 workLoop 函数中有这样一段

const continuationCallback = callback(didUserCallbackTimeout);//callback 就是调度的 callback
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {// 判断 callback 执行之后的返回值类型
  currentTask.callback = continuationCallback;// 如果是 function 类型就把又赋值给 currentTask.callback
  markTaskYield(currentTask, currentTime);
} else {if (enableProfiling) {markTaskCompleted(currentTask, currentTime);
    currentTask.isQueued = false;
  }
  if (currentTask === peek(taskQueue)) {pop(taskQueue);// 如果是 function 类型就从 taskQueue 中删除
  }
}
advanceTimers(currentTime);

​ 在 performConcurrentWorkOnRoot 函数的结尾有这样一个判断,如果 callbackNode 等于 originalCallbackNode 那就复原工作的执行

if (root.callbackNode === originalCallbackNode) {
  // The task node scheduled for this root is the same one that's
  // currently executed. Need to return a continuation.
  return performConcurrentWorkOnRoot.bind(null, root);
}

Lane

​ Lane 的和 Scheduler 是两套优先级机制,相比来说 Lane 的优先级粒度更细,Lane 的意思是车道,相似赛车一样,在 task 获取优先级时,总是会优先抢内圈的赛道,Lane 示意的优先级有以下几个特点。

  • 能够示意不同批次的优先级

    ​ 从代码中中能够看到,每个优先级都是个 31 位二进制数字,1 示意该地位能够用,0 代表这个地位不能用,从第一个优先级 NoLanes 到 OffscreenLane 优先级是升高的,优先级越低 1 的个数也就越多(赛车较量外圈的车越多),也就是说含多个 1 的优先级就是同一个批次。

    export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
    export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;
    
    export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
    export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;
    
    export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
    const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;
    
    const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;
    const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;
    
    export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;
    export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;
    
    const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;
    const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;
    
    const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;
    
    export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;
    
    export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;
    
    const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;
    
    export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
    const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;
    
    export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;
  • 优先级的计算的性能高

    ​ 例如,能够通过二进制按位与来判断 a 和 b 代表的 lane 是否存在交加

    export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {return (a & b) !== NoLanes;
    }

Lane 模型中 task 是怎么获取优先级的(赛车的初始赛道)

​ 工作获取赛道的形式是从高优先级的 lanes 开始的,这个过程产生在 findUpdateLane 函数中,如果高优先级没有可用的 lane 了就降落到优先级低的 lanes 中寻找,其中 pickArbitraryLane 会调用 getHighestPriorityLane 获取一批 lanes 中优先级最高的那一位,也就是通过 lanes & -lanes 获取最左边的一位

export function findUpdateLane(
  lanePriority: LanePriority,
  wipLanes: Lanes,
): Lane {switch (lanePriority) {
    //...
    case DefaultLanePriority: {let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);// 找到下一个优先级最高的 lane
      if (lane === NoLane) {// 上一个 level 的 lane 都占满了降落到 TransitionLanes 持续寻找可用的赛道
        lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
        if (lane === NoLane) {//TransitionLanes 也满了
          lane = pickArbitraryLane(DefaultLanes);// 从 DefaultLanes 开始找
        }
      }
      return lane;
    }
  }
}

Lane 模型中高优先级是怎么插队的(赛车抢赛道)

​ 在 Lane 模型中如果一个低优先级的工作执行,并且还在调度的时候触发了一个高优先级的工作,则高优先级的工作打断低优先级工作,此时应该先勾销低优先级的工作,因为此时低优先级的工作可能曾经进行了一段时间,Fiber 树曾经构建了一部分,所以须要将 Fiber 树还原,这个过程产生在函数 prepareFreshStack 中,在这个函数中会初始化曾经构建的 Fiber 树

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;// 之前曾经调用过的 setState 的回调
  //...
    if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    // 新的 setState 的回调和之前 setState 的回调优先级相等 则进入 batchedUpdate 的逻辑
    if (existingCallbackPriority === newCallbackPriority) {return;}
    // 两个回调优先级不统一,则被高优先级工作打断,先勾销以后低优先级的工作
    cancelCallback(existingCallbackNode);
  }
    // 调度 render 阶段的终点
    newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root),
  );
    //...
}

function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
    //...
  //workInProgressRoot 等变量从新赋值和初始化
  workInProgressRoot = root;
  workInProgress = createWorkInProgress(root.current, null);
  workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
    //...
}

Lane 模型中怎么解决饥饿问题(最初一名赛车最初也要达到起点啊)

​ 在调度优先级的过程中,会调用 markStarvedLanesAsExpired 遍历 pendingLanes(未执行的工作蕴含的 lane),如果没过期工夫就计算一个过期工夫,如果过期了就退出 root.expiredLanes 中,而后在下次调用 getNextLane 函数的时候会优先返回 expiredLanes

export function markStarvedLanesAsExpired(
  root: FiberRoot,
  currentTime: number,
): void {

  const pendingLanes = root.pendingLanes;
  const suspendedLanes = root.suspendedLanes;
  const pingedLanes = root.pingedLanes;
  const expirationTimes = root.expirationTimes;

  let lanes = pendingLanes;
  while (lanes > 0) {// 遍历 lanes
    const index = pickArbitraryLaneIndex(lanes);
    const lane = 1 << index;

    const expirationTime = expirationTimes[index];
    if (expirationTime === NoTimestamp) {

      if ((lane & suspendedLanes) === NoLanes ||
        (lane & pingedLanes) !== NoLanes
      ) {expirationTimes[index] = computeExpirationTime(lane, currentTime);// 计算过期工夫
      }
    } else if (expirationTime <= currentTime) {// 过期了
      root.expiredLanes |= lane;// 在 expiredLanes 退出以后遍历到的 lane
    }

    lanes &= ~lane;
  }
}

export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
     //...
  if (expiredLanes !== NoLanes) {
    nextLanes = expiredLanes;
    nextLanePriority = return_highestLanePriority = SyncLanePriority;// 优先返回过期的 lane
  } else {//...}
  return nextLanes;
}

​ 下图更直观,随之工夫的推移,低优先级的工作被插队,最初也会变成高优先级的工作

正文完
 0