react源码分析(4)-调度过程及原理

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

目录

react-reconciler与scheduler关系

scheduler介绍

JS中的偏函数

总结




2021SC@SDUSC

react-reconciler与scheduler关系

这里给出react-reconciler与scheduler的关系图

此图片的alt属性为空;文件名为Scheduler.png

关于react17的最新版本,我查阅多方资料,但由于在任务调度方面好像改动不小,所以不得不自己参考源码,最终找到了react与scheduler的连通入口,在ReactFiberWorkLoop.new.js中的schedulerCallBack正是这样的方法,它来自于scheduler.js中。根据该方法找到scheduler.js的源码,接下来开始介绍sceduler。

scheduler介绍

作为调度模块,可以首先看一下任务优先级

此图片的alt属性为空;文件名为image-6.png

对应scheduler.js中的scheduler方法的是源码中的 unstable_scheduleCallback方法。

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 = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      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 = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };
  if (enableProfiling) {
    newTask.isQueued = false;
  }

  if (startTime > currentTime) {
    // This is a delayed task.
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      // All tasks are delayed, and this is the task with the earliest delay.
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      } else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    if (enableProfiling) {
      markTaskStart(newTask, currentTime);
      newTask.isQueued = true;
    }
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }

  return newTask;
}

接下来对该方法进行分析,该方法需要传入优先级,(delay是options可选项中的属性,callback的开始时间就是当前时间+delay),然后根据优先级计算出timeout,根据不同的优先级,对应不同的timeout常量,而过期时间就是开始时间+timeout,接着进行比较,如果开始时间小于当前时间,就将其放入timerQueue中然后等待直到taskQueue中的任务执行完为止,通过handleTimeout设立等待时间,当等待时间满足就将其放入taskQueue中,(由图中代码可知,new task 的ID应当为taskIDcounter++)如果开始时间大于当前时间,就直接将其放入taskqueue中(按照过期时间的顺序进行排序)。当前无任务执行时就会执行requestHostCallback以对任务进行调度。在requestHostCallback中会将flushWork赋值给scheduledHostCallback,也就是说,执行任务的函数为flushWork。当前isMessageLoopRunning为false,也就是messageChannel尚未运行,那就执行schedulePerformWorkUntilDeadline,该函数向messageChannel的port2端口发送信息(此时performWorkUntilDeadline已经在port1处监听),所以执行 performWorkUntilDeadline方法,在performWorkUntilDeadline方法中会执行scheduledHostCallback,也就是flushWork,逐步执行完毕taskQueue中的任务。   

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;
    const hasTimeRemaining = true;

    // If a scheduler task throws, exit the current browser task so the
    // error can be observed.
    //
    // Intentionally not using a try-catch, since that makes some debugging
    // techniques harder. Instead, if `scheduledHostCallback` errors, then
    // `hasMoreWork` will remain true, and we'll continue the work loop.
    let hasMoreWork = true;
    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
    } finally {
      if (hasMoreWork) {
        // If there's more work, schedule the next message event at the end
        // of the preceding one.
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
    }
  } else {
    isMessageLoopRunning = false;
  }
  // Yielding to the browser will give it a chance to paint, so we can
  // reset this.
  needsPaint = false;
};

在该方法中获得当前时间,并设置hasTimeRemain为True并执行 scheduledHostCallback 也就是flushWork方法,根据该方法确定是否有更多任务,也就是提到的hasMoreWork,如果有,继续通过schedulePerformWorkUntilDeadline方法调度,如果没有就退出。而在flushWork中会执行workLoop方法,workLoop方法调用时会执行任务。

function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime);
  currentTask = peek(taskQueue);
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      // This currentTask hasn't expired, and we've reached the deadline.
      break;
    }
    const callback = currentTask.callback;
    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      if (enableProfiling) {
        markTaskRun(currentTask, currentTime);
      }
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        currentTask.callback = continuationCallback;
        if (enableProfiling) {
          markTaskYield(currentTask, currentTime);
        }
      } else {
        if (enableProfiling) {
          markTaskCompleted(currentTask, currentTime);
          currentTask.isQueued = false;
        }
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
      advanceTimers(currentTime);
    } else {
      pop(taskQueue);
    }
    currentTask = peek(taskQueue);
  }
  // Return whether there's additional work
  if (currentTask !== null) {
    return true;
  } else {
    const firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
    
  }
}

在workLoop方法中会得到taskQueue中最需要执行的任务然后执行,由于是分时间片执行,在执行过程中会在每个时间片执行一次任务函数,并且反复检测任务函数的返回值,假如返回值为函数,说明尚未完成,那么循环执行,假如返回值为null,说明该任务函数已完成,那就在taskQueue中寻找下一个任务继续执行。而workLoop循环退出有两种可能,没有剩余时间分给时间片或者没有下一个任务,假如此时hasTimeRemaining为false,说明没有剩余时间,就需要退出,但保留当前任务,那么此时workLoop的返回值为true,代表hasmorework,那就继续通过messageChannel进行调度。假如currentTask为null,说明taskQueue中任务已完成,已经没有下一个任务,那就就执行requestHostTimeout方法等待从timerQueue中获取任务放入taskQueue中并且返回false,代表没有更多工作,调度已经完成。这是scheduler中比较重要的部分。

JS中的偏函数

这里说一下JS中的偏函数,由bind实现,可以进行预定义参数,因为在workLoop中只会传入didUserCallbackTimeout这一布尔值,所以需要预先绑定root。

    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );

另外,在ensureRootIsScheduled方法中可以检查任务,如果出现新的较高优先级的任务,就会使用执行cancelCallback,将当前任务的callback设置为null,从而使workLoop将正在执行的任务送出taskQueue,造成中断,实现优先级调度。     

总结

介绍了react-reconciler与scheduler的关系,分析了react中如何通过scheduler实现对多任务的优先级调度以及在scheduler的workLoop中单个任务是否完成的判断,还补充解释了JS中关于偏函数的知识。

# React源码解析之Reconciler运行循环与scheduler调度 React源码之看完吊打面试官系列 经历一个月的学习整理,站在前人的肩膀上,对React有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助。如果此系列文章对您有些帮助,还望在座各位义夫义母不吝点赞关注支持🐶,也希望各位大佬拍砖探讨 本系列行文思路如下,本篇属于React中的React的管理员(reconciler与scheduler) [X] React启动过程[X] React的两大工作循环[X] React中的对象[ ] React fiber的初次创建与更新[ ] Reac 阅读详情

相关推荐

深入浅出React(Scheduler调度器的源码分析)

timerQueue:存储未过期的任务。taskQueue:存储已过期的任务。为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。

发果叁 473

React中的 Scheduler

React 调度是用来调度 fiber 任务的,任务可以暂停并将控制权交给浏览器,调度的核心是通过 Priority Queue 实现,根据重要程度进行排序,过期时间作为排序字段,先到期的先执行。

hawk2014bj的博客 1238

react源码分析:babel如何解析jsx

不管是类组件还是函数组件,最终我们写的jsx都被babel转化成了可识别的元素,其中我们也看了Component等内部实现,了解到了作为他是怎么被创建的,但是远远没有完,因为我们知道我们在写React的时候,会在后面带上一个,没错我们下一章节就要去探索一下方法了。一章我想跟大家探讨的是React的生命周期与事件系统。这一章主要是介绍组件在mountupdatedestroy阶段的生命周期执行顺序与React事件系统的注册,绑定,调度更新等。

weixin_59558923的博客 678

React 第三十六章 Scheduler 任务调度

用于在 React 应用中进行任务调度。它可以帮助开发人员在处理复杂的任务和操作时更好地管理和优化性能。关于 Scheduler 在React 如何渲染的可以参考下面我们根据流程图先简单的了解 Scheduler 的调度过程Scheduler 维护两个队列,分别存放普通任务和延时任务当接收的是普通任务时,会加入普通任务队列,然后执行。根据环境创建宏任务(主要创建的就是),然后执行。该方法实际上主要就是在调用。

aXin_li的博客 1587

React调度系统 Scheduler

大家好,我是前端西瓜哥。今天来学习 React调度系统 Scheduler。React 使用了全新的 Fiber 架构,将原本需要一次性递归找出所有的改变,并一次性更新真实 DOM 的流程,改成通过时间分片,先分成一个个小的异步任务在空闲时间找出改变,最后一次性更新 DOM。这里需要使用调度器,在浏览器空闲的时候去做这些异步小任务。做这个调度工作的在 React 中叫做 Scheduler(调度器)模块。其实浏览器是提供一个 requestIdleCallback 的方法,让我们可以在浏览器空闲的时去调

fe_watermelon的博客 1764

React架构

我们知道,JS可以操作DOM,GUI渲染线程与JS线程是互斥的。所以和不能同时执行。所以React在浏览器每一帧的时间中,预留一些时间给JS线程,React利用这部分时间更新组件,预留的初始时间是5ms,这个再React17源码中体现。

m0_56856837的博客 256

reactjs源码分析-上篇(首次渲染实现原理

reactjs源码分析-上篇(首次渲染实现原理) reactjs是目前比较火的前端框架,但是目前并没有很好的解释原理的项目。reactjs源码比较复杂不适合初学者去学习。所以本文通过实现一套简易版的reactjs,使得理解原理更加容易。 所有实例源码都托管在github。点这里里面有分步骤的例子,可以一边看一边运行例...

MichaelAn的博客 310

React源码27】深入学习React 源码实现——useTransition 的实现原理源码分析(最完整版)

useTransition 是 React 18 引入的关键 Hook,用于优化并发更新。它通过将状态更新标记为"非紧急",让高优先级任务优先执行,提升用户体验。本文详解其实现原理:利用 React 的 Lane 优先级系统,在初始化阶段创建过渡队列,当调用 startTransition 时,将回调加入队列并标记为 TransitionPriority,由调度器在空闲时执行。源码分析显示其核心在 ReactFiberHooks.js 中实现,配合调度系统完成优先级管理。

silverclayz的技术分享 995

reactjs源码分析

前端的发展特别快,经历过jQuery一统天下的工具库时代后,现在各种框架又开始百家争鸣了。angular,ember,backbone,vue,avalon,ploymer还有reactjs,作为一个前端真是稍不留神就感觉要被淘汰了,就在去年大家还都是angularjs的粉丝,到了今年又开始各种狂追reactjs了。前端都是喜新厌旧的,不知道最后这些框架由谁来一统天下,用句很俗的话说,这是最好的时

8871

Reactjs源码分析

前端的发展特别快,经历过jQuery一统天下的工具库时代后,现在各种框架又开始百家争鸣了。angular,ember,backbone,vue,avalon,ploymer还有reactjs,作为一个前端真是稍不留神就感觉要被淘汰了,就在去年大家还都是angularjs的粉丝,到了今年又开始各种狂追reactjs了。前端都是喜新厌旧的,不知道最后这些框架由谁来一统天下,用句很俗的话说,这是最好的时代

极客神殿 1379

react源码浅析

项目地址

weixin_38405133的博客 327

react源码分析

原文地址:http://www.html-js.com/article/JS-analysis-of-the-single-row-from-zero-reactjs-source-first-rendering-principle%203154 前端的发展特别快,经历过jQuery一统天下的工具库时代后,现在各种框架又开始百家争鸣了。angular,ember,backbone,vue,a

Generon的博客 2602

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

`scheduler`这个包主要是在react做diff做任务分配机制, 核心机制类似于[requestidlecallback](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback), >`window.requestIdleCallback()`会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟敏感的事件产生影响。

翾的博客 1800

React Hooks原理 - useDeferredValue】

从基本使用和原理实现,介绍了useDeferredValue这个Hook,以及拓展介绍了其和Suspense组件的关系

weixin_44273311的博客 1028

react源码分析:实现react时间分片

我们常说的调度,可以分为两大模块,时间分片和优先级调度本节将从时间分片的实现剖析的异步渲染原理,阅读本文你讲可以了解上文提到过,时间分片其实就是一个固定而连续且有间隔的时间区间固定:时间分片是工作时长是固定的 连续:分片之间是连续的,当前分片内有工作没做完,会留到下个分片继续 有间隔:在进入下一个分片前,会有一定时间的间隔这些解释比较抽象,可以更加通俗去理解固定:每天固定工作8小时 连续:每天都要上班 有间隔:明天上班前会休息一段时间我们知道,最重要,也是最耗时的任务是节点遍历。设想一个页面上有一万个DOM

weixin_59558923的博客 836

React调度器终极指南:深入理解Scheduler时间切片与优先级调度原理

React作为目前最流行的前端框架之一,其高效的渲染机制离不开Scheduler(调度器)的支持。Scheduler是React架构中的核心组件,主要负责时间切片和优先级调度两大功能,确保应用在处理复杂UI更新时依然保持流畅的用户体验。本文将从底层原理出发,详细解析React Scheduler的工作机制,帮助开发者深入理解React性能优化的关键技术。 ## 时间切片:打破长任务的执行瓶颈

gitblog_01112的博客 351

深入分析React-Scheduler原理

关键词:react react-scheduler scheduler 时间切片 任务调度 workLoop。

xiaofeng123aazz的博客 322

React核心原理深度解析:从理念到架构的完整指南

React作为现代前端开发的基石,其设计理念和架构思想深刻影响着整个前端生态。just-react项目通过自顶向下的方式,为我们揭示了React从理念到实现的全貌。本文将带你深入理解React的核心概念,掌握其架构精髓。 ## 🔍 React设计理念:快速响应的用户体验 React的核心目标是实现**快速响用的用户体验**。这包含了两个关键方面: - **CPU瓶颈**:当大量DOM操作导

gitblog_00564的博客 737
上一篇: react源码分析(3)-Fiber节点,树的介绍以及渲染过程总览
下一篇: react源码分析(5)-介绍beginWork
zyuxing
博客等级 码龄7年 17粉丝 28原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值