React scheduler 源码分析(16.8版本)

优化背景:
在react16 版本之前,react 的更新渲染存在着一些性能的问题,这个问题产生的原因是因为在更新/渲染时,采用递归的方式(栈调度器),而递归有一个不好的地方就是,他不能被随意的break;也不能在break之后也很难恢复到break之前的状态,而因为浏览器是单线程的,V8 引擎执行js 和 渲染引擎渲染页面是两个互斥的行为,这也就是说当更新任务太庞大的时候,js执行太久而致使页面丢帧,不流畅,甚至卡顿。
优化方式
react16 之后,react团队在react中加入了一个全新的架构:Fiber,并赋予了他光荣而艰巨的使命----解决16版本之前的性能问题;
Fiber 架构实际上是个链表结构,每个节点包含了 return,subling,children。他们分别代表父节点,兄弟节点,子节点。有了链表结构,那就可以进行DFS,而且也可以很轻松的break & continue;

在这里插入图片描述
当然,上述都是题外话,今天主要分析react 中的 scheduler。

今天的正题:scheduler

个人拙见,如有问题希望大佬指正。知也无涯,共勉!

今天主要分析的源码的就是,react 中的任务调度器。在16.8版本之后,scheduler已经被react 团队从react-dom中抽离了出来,作为一个独立的包单独发布到npm上。

  if (expirationTime === Sync) {
    callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
  } else {
    callbackNode = scheduleCallback(priorityLevel, performConcurrentWorkOnRoot.bind(null, root), 
    {
      timeout: expirationTimeToMs(expirationTime) - now()
    });
  }

这段代码位于react-dom 的源码中,说明了,其中的expirationTime代表了过期时间,当为Sync的时候继续沿用react16版本之前的更新,否则就会采用全新的调度规则。
而其中的performConcurrentWorkOnRoot 就是一个即将执行的任务,它被传入scheduler中,进行调度处理。

unstable_scheduleCallback
这是schedule中抛出的一个核心方法,该方法主要执行了以下几个操作
1: 声明一个信的Task对象属性如下

var newTask = {
    id: taskIdCounter++,
    callback: callback, // 对应上一个代码片段的 performConcurrentWorkOnRoot方法
    priorityLevel: priorityLevel,  //优先级
    startTime: startTime, //开始时间
    expirationTime: expirationTime, //过期时间
    sortIndex: -1  //初始值是 -1 后续会根据是否delay 而被赋予不同的值,如果delay = starttime 反之 expirationTime
  };

2: 执行代码

  if (startTime > currentTime) {
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      if (isHostTimeoutScheduled) {
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    } 
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }

以上代码主要做的事情就是,首先判断是否是delay的任务,如果是,就执行requestHostTimeout,如果不是就执行requestHostCallback,当然中间还处理了taskQuene 和timerQuene。这两个队列顾名思义,taskQuene是任务队列,timerQuene就是为了后续让,被delay的任务能在delay ms 之后执行的一个依据 (通过 advanceTimers 方法 advanceTimers讲队列按时常排序)

首先先看正常任务的执行流程:
先将newTask 的isQueued 置为true 然后执行requestHostCallback函数,并将flushWork作为参数传进去,接下来看requestHostCallback代码

  requestHostCallback = function (cb) {
    if (_callback !== null) {
      // Protect against re-entrancy.
      setTimeout(requestHostCallback, 0, cb);
    } else {
      _callback = cb;
      setTimeout(_flushCallback, 0);
    }
  };

可以看到,其实就是一个递归,反复执行requestHostCallback,并传入flushWork,其实可以看到,requestHostCallback其实目的就是了反复修改一个全局变量 _callback,并执行 _flushCallback, 它们有什么意义呢?我们一会再看
接下来在看,如果是delay的任务,则会执行,requestHostTimeout,代码如下

  requestHostTimeout = function (cb, ms) {
    _timeoutID = setTimeout(cb, ms);
  };

它实际上就是将handleTImeout 方法延迟delay ms之后在执行,并声称一个 _timeoutID 方便后续使用 cancelHostTimeout() clear掉,handleTimeout 代码如下

function handleTimeout(currentTime) {
  isHostTimeoutScheduled = false;
  advanceTimers(currentTime);
  if (!isHostCallbackScheduled) {
    if (peek(taskQueue) !== null) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    } else {
      var firstTimer = peek(timerQueue);
      if (firstTimer !== null) {
        requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
      }
    }
  }
}

所以很显然, 正常任务 和 delay 任务的执行差别就是,正常任务会立刻执行requestHostCallback 而 delay 的则会在delay ms 之后通过setTimeOut来执行requestHostCallback /很像是废话,但就是如此,,,/

上面说到,requestHostCallback 其实就是在维护一个全局变量 _callback 以及调用_flushCallback 方法,_flushCallback 方法定义如下

  var _flushCallback = function () {  //简化后的去掉了 异常捕捉处理
    if (_callback !== null) {
        var currentTime = exports.unstable_now();
        var hasRemainingTime = true;
        _callback(hasRemainingTime, currentTime);
        _callback = null;
        }
  };

由此可见,它其实就是执行了 _callback 并传入了两个参数,hasRemainingTime, currentTime,而这个_callback 变量则是 刚刚传入的实参 flushWork,所以我们来看 flushWork 的定义

function flushWork(hasTimeRemaining, initialTime) {
  isHostCallbackScheduled = false;
  if (isHostTimeoutScheduled) {  //如果在执行 delay的任务,cancel掉
    // We scheduled a timeout but it's no longer needed. Cancel it.
    isHostTimeoutScheduled = false;
    cancelHostTimeout();
  }
  isPerformingWork = true;
  var previousPriorityLevel = currentPriorityLevel;
  try {
    if (enableProfiling) {  //这个变量一直是true ,所以其他代码就忽略掉了
      try {
        return workLoop(hasTimeRemaining, initialTime);  //如果workLoop 正常,执行,否则走catch
      } catch (error) { //这里面实际上
        if (currentTask !== null) {
          var currentTime = exports.unstable_now();
          markTaskErrored(currentTask, currentTime);
          currentTask.isQueued = false;
        }
        throw error;
      }
    } 
  } finally {
   //这里面简单说一下,就是执行workloop 之后将一些变量置为初始值
}

这里面很明显,如果workLoop 不抛出异常,执行完毕之后就会将变量置为初始值,否则,会将当前的currentTask.isQueued 置为false,所以我们还是要继续看 workLoop这个方法的功能是什么;

function workLoop(hasTimeRemaining, initialTime) {
  var currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (currentTask !== null && !(enableSchedulerDebugging )) {
    if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost())) {
      break;
    }
    var callback = currentTask.callback;
    if (callback !== null) {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      var didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      markTaskRun(currentTask, currentTime);
      var continuationCallback = callback(didUserCallbackTimeout);
      currentTime = exports.unstable_now();
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        markTaskYield(currentTask, currentTime);
      } else {
        {
          markTaskCompleted(currentTask, currentTime);
          currentTask.isQueued = false;
        }
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }
    currentTask = peek(taskQueue);
  }	
  if (currentTask !== null) {
    return true;
  } else {
    var firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
  }
}

首先,先从task队列中拿出最先插入的任务,而while 的作用就是,将整个task队列里每一个的 task 的 callback,而其中的

var continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
      }

则说明了,会反复的执行 callback 也就是,文章开始提到的 react-dom里传入的实参任务 performConcurrentWorkOnRoot.bind(null, root);期间并调整timer 队列的顺序。至此,代码的走向又指向了 react-dom 里的 performConcurrentWorkOnRoot 方法。scheduler 的任务也就完成了。

总结一下,它做了什么:
1: 声明一个task任务,其中包含了id,callback,starttime,exprationtime,priorityLevel,sortIndex,isQueued这七个属性,
2: 声明两个队列结构的变量,taskQuene ,timerQuene,其中的taskQuene就是正常的任务,而timerQuene就是需要延缓执行的任务。
3: 遍历taskQuene 中的每个task,并且执行完毕所有的callback,其中callback属性是会递归覆盖的,所以会清空performConcurrentWorkOnRoot 被绑定的 root 里的任务,
而这个 root 实际上就是 最顶层fiberRoot下的 fiber.stateNode;这里就暂不赘述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值