react源码解析15.scheduler&Lane

当我们在类似下面的搜索框组件进行搜索时会发现,组件分为搜索部分和搜索结果展示列表,我们期望输入框能立刻响应,结果列表可以有等待的时间,如果结果列表数据量很大,在进行渲染的时候,我们又输入了一些文字,因为用户输入事件的优先级是很高的,所以就要停止结果列表的渲染,这就引出了不同任务之间的优先级和调度

react源码15.5

Scheduler

我们知道如果我们的应用占用较长的js执行时间,比如超过了设备一帧的时间,那么设备的绘制就会出不的现象。

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

react源码15.3

时间切片

​ 在浏览器的一帧中js的执行时间如下

react源码15.1

​ 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值