React源码分析(一)=> scheduler分析

本文深入分析React源码中的scheduler模块,探讨`getCurrentTime`、`unstable_scheduleCallback`等关键函数,揭示React如何利用`requestAnimationFrame`和`postMessage`在浏览器空闲时期执行任务,实现类似`requestIdleCallback`的功能。同时,文章通过双向链表组织任务,详细阐述任务调度的执行流程和任务优先级。通过对React源码的解析,理解React的异步更新机制和任务调度策略。
摘要由CSDN通过智能技术生成

1. 前言

为了读代码更加有效率,提前看了一篇如何阅读源码的文章:https://zxc0328.github.io/2018/05/01/react-source-reading-howto/
因此此次本人阅读源码主要想看懂以下6个问题:

  1. ReactDOM.render()是如何挂载到真实DOM上的
  2. setState实现原理,为什么是异步的
  3. 生命周期结合2号问题一起看
  4. react16的fiber架构是什么
  5. jsx是如何解析的
  6. react hook是如何做到的

分析代码基于React V16.8.6,react源码目录截图如下图所示:
在这里插入图片描述

所需要看的代码一个库就够了,https://github.com/facebook/react/


先看的react-dom代码, 一点点单步到了scheduler, 这个包的代码看起来不多(可能是我第一次看框架源码, 看的有点恶心… 一堆全局变量, 各个函数来回调用, 看了好几天, 如果下面哪里有问题, 还请各位同行指点), 那就先来梳理下这个包吧.

scheduler这个包主要是在react做diff做任务分配机制, 核心机制类似于requestidlecallback,

window.requestIdleCallback()会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟敏感的事件产生影响。

但这个函数支持度太惨
在这里插入图片描述
react则使用requestAnimationFramepostMessage来模拟实现的requestidlecallback. 工作原理是调度requestAnimationFrame,存储帧开始的时间,然后调度postMessage,后者在绘制后进行调度。

该包主要流程是把所有任务通过双向链表连接起来, 通过requestAnimationFrame来在浏览器每帧的空闲时间循环处理所有任务, 直到链表为空为止.


2. getCurrentTime

这个函数后面会经常用到的, 先到前面来说下, 先看代码:

// packages\scheduler\src\forks\SchedulerHostConfig.default.js
const hasNativePerformanceNow =
  typeof performance === 'object' && typeof performance.now === 'function';
const localDate = Date;

if (hasNativePerformanceNow) {
   
  const Performance = performance;
  getCurrentTime = function() {
   
    return Performance.now();
  };
} else {
   
  getCurrentTime = function() {
   
    // 该方法在 ECMA-262 第五版中被标准化, Date.now() === new Date().getTime();
    // 出处 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
    return localDate.now();
  };
}

performance.now()Date.now() 不同的是,返回了以微秒(百万分之一秒)为单位的时间,更加精准。

并且与 Date.now() 会受系统程序执行阻塞的影响不同,performance.now() 的时间是以恒定速率递增的,不受系统时间的影响(系统时间可被人为或软件调整)。

注意Date.now()输出的是 UNIX 时间,即距离 1970 的时间,而performance.now()输出的是相对于 time origin(页面初始化: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin) 的时间。

使用 Date.now() 的差值并非绝对精确,因为计算时间时受系统限制(可能阻塞)。但使用 performance.now() 的差值,并不影响我们计算程序执行的精确时间。

3. unstable_scheduleCallback函数

  • 函数前面的unstable表示不稳定的意思, 之后还会有变动.
  • 这个方法主要就是将任务组成双向链表, 并按照过期时间作为优先级.

先定义优先级, 代码如下:

// packages\scheduler\src\Scheduler.js

// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// 这是32位系统V8引擎里最大的整数
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
var maxSigned31BitInt = 1073741823;
// Times out immediately 立即过期
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY = maxSigned31BitInt;

主函数代码如下:

// packages\scheduler\src\Scheduler.js

// 组成双向链表, 开始安排任务
function unstable_scheduleCallback(priorityLevel,  callback,  deprecated_options) {
   
  var startTime =
    currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();

  // 过期时间 = 加入时间 + 优先级时间
  var expirationTime;
  if (
    typeof deprecated_options === 'object' &&
    deprecated_options !== null &&
    typeof deprecated_options.timeout === 'number'
  ) {
   
    expirationTime = startTime + deprecated_options.timeout;
  } else {
   
    // 根据不同的优先级, 赋予不同的过期时间
    switch (priorityLevel) {
   
      case ImmediatePriority:
        expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
        break;
      case UserBlockingPriority:
        expirationTime = startTime + USER_BLOCKING_PRIORITY;
        break;
      case IdlePriority:
        expirationTime = startTime + IDLE_PRIORITY;
        break;
      case LowPriority:
        expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
        break;
      case NormalPriority:
      default:
        expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
    }
  }
  
// 未完

上面先计算一下callback的过期时间, 接下来创建链表节点, 并组成链表, 代码如下:

// packages\scheduler\src\Scheduler.js

// 续上
// 基于上面的优先级和过期时间创建一个节点
var newNode = {
   
    callback,
    priorityLevel: priorityLevel,
    expirationTime,
    next: null,
    previous: null,
  };

  if (firstCallbackNode === null) {
   
    // This is the first callback in the list. 如果firstCallbackNode没有, 说明是第一个节点
    firstCallbackNode = newNode.next = newNode.previous = newNode;
    scheduleHostCallbackIfNeeded(); // 之后再说, 先忽略
  } else {
   
    var next = null;
    var node = firstCallbackNode;
    do {
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值