React16源码: React中的异步调度scheduler模块以及时间片源码实现

React Scheduler


1 ) 概述

  • react当中的异步调度,称为 React Scheduler
  • 发布成单独的一个 npm 包就叫做 scheduler
  • 这个包它做了什么?
    • A. 首先它维护时间片
    • B. 然后模拟 requestIdleCallback 这个API
      • 因为现在浏览器的支持不是特别的多
      • 所以在浏览当中只是去模拟了一个这个API,而不是直接使用这个API
      • 因为需要考虑到浏览器兼容性
      • 这个API的作用
        • 调用这个API传入一个回调之后,这个API会等到浏览器把它的一些主要任务执行完了
        • 当它有空闲的时间的时候,再回来调用这个回调
      • 相对于 requestAnimationFrame 来说,它的优先级会低很多
      • 它是等浏览器器要做的事情做完了之后,再回来调这个回调
      • requestAnimationFrame 是浏览器要渲染当前帧的时候,调用这个回调
    • C. 调度列表和进行一个超时的判断
  • 关于时间片
    • 不管是在浏览器还是在App当中,要给用户很流畅的一个感觉的时候
    • 至少要保证在一秒钟之内要渲染30帧以上
    • 现在的一些高刷新率的浏览器,可能会要求在60帧以上,甚至还有更高的,比如,120帧
    • 这个帧数就是我们1秒钟,页面要重新渲染刷新多少次
    • 它并不是说我一秒钟之内刷新30次,满足就行了。
    • 比如前面的半秒钟只刷新了一次,后面的半秒钟刷新了二十九次,这个也是不行的
    • 这个给用户的感觉,就是前面这半秒钟会特别的卡就一动不动,然后后面又变得流畅
    • 所以,它的要求还需要是平均的每33毫秒要刷新1帧,要保持这个频率
    • 浏览器必须自己去渲染这些动画,要每1帧里面有固定的时间去渲染这个动画
    • 在这里举个例子,比如说整个应用所有的js的操作,都是通过 react 来实现的
    • 而浏览器有一个一直在更新的动画, 浏览器渲染这个动画如果要11毫秒
    • 那么给每一帧的, 就是把一秒钟分成了30帧之后,每一帧是33毫秒
    • 这个33毫秒里面的11毫秒是必须要留给浏览器去渲染这个动画的, 才能让这个动画看起来是流畅的
    • 而在这个时候留给react去渲染它的应用更新的时候,每一帧里面就只有22毫秒
    • 如果react它在这一帧里面的一个更新,它需要渲染的时间很长,比如说35毫秒
    • 那这个时候,我们一帧的时间就全部给react渲染给占掉了
    • 因为 js 引擎是单线程的, 如果react在一直在执行,浏览器它就没有机会去获得运行权
    • 就没有机会去刷新它的一个动画, 这时候,不仅把一帧的时间占完了
    • 这样还不够,还要去下一帧里面借用一点时间,那么这个时间用完之后
    • 浏览器要去更新动画,如果这一帧里面我们就用掉了13毫秒,剩下的时间就只剩下20毫秒
    • 那么这20毫秒,又可能要运行一部分react的更新,然后再去浏览器的一个渲染
    • 这就会导致整个动画变得卡顿起来了
    • 这就是 React Scheduler 它的一个目的, 为了保证react它去执行更新的这个时间
    • 不超过在浏览器的每一帧里面特定的时间,它希望留给浏览器去刷新动画,或者是响应用户输入的反馈的时候
    • 每一帧里面有足够的时间

2 )时间片源码

  • 时间片源码在 packages/scheduler 这个包里面,是一个单独的模块,单独发布到 npm 上

  • 在 ReactFiberScheduler.js 里面,哪个地方用到它呢?

    • requestWork 函数里面,如果 expirationTime 异步的,就会调用 scheduleCallbackWithExpirationTime
      function scheduleCallbackWithExpirationTime(root: FiberRoot,expirationTime: ExpirationTime,
      ) {if (callbackExpirationTime !== NoWork) {// A callback is already scheduled. Check its expiration time (timeout).if (expirationTime > callbackExpirationTime) {// Existing callback has sufficient timeout. Exit.return;} else {if (callbackID !== null) {// Existing callback has insufficient timeout. Cancel and schedule a// new one.cancelDeferredCallback(callbackID);}}// The request callback timer is already running. Don't start a new one.} else {startRequestCallbackTimer();}callbackExpirationTime = expirationTime;const currentMs = now() - originalStartTimeMs;const expirationTimeMs = expirationTimeToMs(expirationTime);const timeout = expirationTimeMs - currentMs;callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
      }
      
      • 全局变量 callbackExpirationTime 对应的是 上一次调用 React Scheduler 去申请了一个callback
      • 这个callback 也会有一个 expirationTime, 因为是异步调度,所以会有一个 expirationTime 传进来
      • 如果这个 callbackExpirationTime !== NoWork 代表之前有一个callback在执行了
      • 这边就会判断当前的 expirationTime 是否比之前回调中的那个要大
      • 如果大,说明当前的这个的优先级要低,这个时候就直接return了不执行
      • 因为它优先级更低,我们肯定要执行优先级更高的那个,调用 cancelDeferredCallback 把之前的 cancel 掉
      • startRequestCallbackTimer 这个函数跳过,不涉及主流程,涉及DEV Tool 相关
      • 接着更新一系列的变量
        • 更新 callbackExpirationTime
        • 计算出 timeout
      • 最后调用 scheduleDeferredCallback 这个方法来自于 ReactFiberHostConfig.js
        • 如果直接查找 这个文件,发现基本上没有什么内容, 是因为 React对于打包工具的配置,进行了文件名的映射
        • 它实际映射的是 eact-reconciler/src/forks/ReactFiberHostConfig.dom.js
          export * from 'react-dom/src/client/ReactDOMHostConfig';
          
        • 发现里面就一行代码,找到对应的 ReactDOMHostConfig.js 文件,搜索 scheduleDeferredCallback 方法
          export {unstable_scheduleCallback as scheduleDeferredCallback,
          } from 'scheduler';
          
          • 可追溯到 这个方法来自于 scheduler 包
          • 这个方法涉及比较多,先跳过
        • callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
      • 它最后返回 一个 callbackID, 这个id用于后期 cancel 的标识,cancelDeferredCallback(callbackID);
        • 这里之前也说了,如果新的任务优先级更高,需要把老的取消,再调用新的callback
      • 而里面的参数 performAsyncWork
        • requestWork 中,当 expirationTime === Sync 时,调用的也是 performSyncWork 这个是同步的
        • 而如果是异步,则调用 scheduleCallbackWithExpirationTime 函数,最终调用的是这里的 performAsyncWork
        • 所以,这两个是对应的,同步和异步
  • 进入 scheduleDeferredCallback 函数的源码 packages/scheduler/src/Scheduler.js 找到 unstable_scheduleCallback

    function unstable_scheduleCallback(callback, deprecated_options) {var startTime =currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();var expirationTime;if (typeof deprecated_options === 'object' &&deprecated_options !== null &&typeof deprecated_options.timeout === 'number') {// FIXME: Remove this branch once we lift expiration times out of React.expirationTime = startTime + deprecated_options.timeout;} else {switch (currentPriorityLevel) {case ImmediatePriority:expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;break;case UserBlockingPriority:expirationTime = startTime + USER_BLOCKING_PRIORITY;break;case IdlePriority:expirationTime = startTime + IDLE_PRIORITY;break;case NormalPriority:default:expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;}}var newNode = {callback,priorityLevel: currentPriorityLevel,expirationTime,next: null,previous: null,};// Insert the new callback into the list, ordered first by expiration, then// by insertion. So the new callback is inserted any other callback with// equal expiration.if (firstCallbackNode === null) {// This is the first callback in the list.firstCallbackNode = newNode.next = newNode.previous = newNode;ensureHostCallbackIsScheduled();} else {var next = null;var node = firstCallbackNode;do {if (node.expirationTime > expirationTime) {// The new callback expires before this one.next = node;break;}node = node.next;} while (node !== firstCallbackNode);if (next === null) {// No callback with a later expiration was found, which means the new// callback has the latest expiration in the list.next = firstCallbackNode;} else if (next === firstCallbackNode) {// The new callback has the earliest expiration in the entire list.firstCallbackNode = newNode;ensureHostCallbackIsScheduled();}var previous = next.previous;previous.next = next.previous = newNode;newNode.next = next;newNode.previous = previous;}return newNode;
    }
    
    • 首先看 参数 callback, deprecated_options
      • callback 是传进来的 performAsyncWork
      • deprecated_options 是即将被废弃的 optinos,这个即将被废弃
    • 接着处理 var startTime = currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
      • getCurrentTime 是重新计算一个 xx.now()
        if (hasNativePerformanceNow) {var Performance = performance;getCurrentTime = function() {return Performance.now();};
        } else {getCurrentTime = function() {return localDate.now();};
        }
        
      • 这里,浏览器平台是这个 localDate.now();
    • 下面有个判断if (typeof deprecated_options === 'object' && deprecated_options !== null && typeof deprecated_options.timeout === 'number')
      • 接着判断 deprecated_options 这个参数,存在则计算出 expirationTime
        // FIXME: Remove this branch once we lift expiration times out of React.
        expirationTime = startTime + deprecated_options.timeout;
        
        • 当把 expirationTime 相关的逻辑提取出来之后,这个 if判断就被删除了,后面只有 else 里面的东西了
        • 所以说,这个 deprecated_options 即将被废弃
    • 如果走到 else 里面,进行switch case currentPriorityLevel
      • 可以看下各个常量的值
        var maxSigned31BitInt = 1073741823;// Times out immediately
        var IMMEDIATE_PRIORITY_TIMEOUT = -1;
        // Eventually times out
        var USER_BLOCKING_PRIORITY = 250;
        var NORMAL_PRIORITY_TIMEOUT = 5000;
        // Never times out
        var IDLE_PRIORITY = maxSigned31BitInt;
        
      • 也就是说,将来很可能会把 expirationTime 相关逻辑移入 scheduler 包中
      • 之前在 packages/react-reconciler/src/ReactFiberReconciler.js 中
      • 不过,在目前的逻辑中 else 里面的东西,用不到
    • 接下去,创建 newNode 的对象
      var newNode = {callback,priorityLevel: currentPriorityLevel,expirationTime,next: null,previous: null,
      };
      
      • next 和 previous 是用来存储链表的数据结构的
    • 接下来 if (firstCallbackNode === null)
      • firstCallbackNode 是 scheduler 中维护的一个单项列表的头部
      • 如果匹配判断,说明传递进来的 callback 是第一个
        • 进行赋值处理 firstCallbackNode = newNode.next = newNode.previous = newNode;
        • 并调用 ensureHostCallbackIsScheduled();
    • 不匹配的时候
      • 有一个或多个callback, 则进行循环
      • 在循环中判断,node.expirationTime > expirationTime
        • 如果匹配,next = node; 并跳出循环
        • 这是 scheduler 对于传进来的所有callback, 按照 expirationTime 的大小,也就是优先级的高低进行排序
        • 它会把优先级更高的任务,排到最前面
      • 如果 next 是 null
        • 这个节点要插在callbackList里面的最后一个
      • 如果 next 是 firstCallbackNode,即第一个
        • 因为当前节点要插在这个单项列表最前面,优先级最高
        • 马上 firstCallbackNode 变化了,即更新了 firstCallbackNode = newNode;
        • 调用 ensureHostCallbackIsScheduled();
          • 这个函数在上面两处调用了,但是没有在 if (next === null) 中调用
            • 因为 这个条件下,firstCallbackNode 仍然处于第一位
            • 后续要调用的话,第一个被调用的还是 firstCallbackNode
            • 所以,顺序不会变,所以不需要重新调用 ensureHostCallbackIsScheduled();
          • 注意,调用上述方法会进入一个循环,循环的调用List里面的东西
          • 当 firstCallbackNode 变化了,才会去调用,因为头部变了
  • 下面为这个方法链表的处理示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/623221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用scipy处理图片——旋转任意角度

大纲 载入图片左旋转30度,且重新调整图片大小右旋转30度,且重新调整图片大小左旋转135度,保持图片大小不变右旋转135度,保持图片大小不变 在《使用numpy处理图片——90度旋转》中,我们使用numpy提供的方法,…

命令行登录Mysql的详细讲解

目录 前言1. 本地登录2. 远程登录3. 拓展 前言 对于命令行登录Mysql一般都是用mysql -u root -p 但对于如何远程登陆,一直其他的参数还是有些盲区,对此总结科普 对于登录过程中出现的问题,可看我之前的文章: 服务器 出现ERROR …

【矩阵快速幂】封装类及测试用例及样例

作者推荐 视频算法专题 通俗的说&#xff0c;就是矩阵的乘方。 封装类 核心代码 class CMat { public:// 矩阵乘法static vector<vector<long long>> multiply(const vector<vector<long long>>& a, const vector<vector<long long>…

Python 面向对象之元类

Python 面向对象之元类 【一】一切皆对象 【1】元类 元类&#xff08;metaclass&#xff09;是Python中用于创建类的类。在Python中&#xff0c;类是对象&#xff0c;而元类就是类的类它们控制类的创建过程&#xff0c;允许你定制类的行为Python中内置的默认元类是type我们用…

Rust-语句和表达式

if-else Rust中if-else表达式的作用是实现条件分支。if-else表达式的构成方式为&#xff1a;以if关键字开头&#xff0c;后面跟上条件表达式&#xff0c;后续是结果语句块&#xff0c;最后是可选的else块。条件表达式的类型必须是bool。 if-else结构还可以当表达式使用 loop …

如何用LLM和自有知识库搭建智能agent?

用LangChain建立知识库&#xff0c;文末中也推荐其他方案。 项目源码&#xff1a;ChatPDF实现 LangChain Indexes使用 对加载的内容进行索引&#xff0c;在indexes中提供了一些功能&#xff1a; Document Loaders&#xff0c;加载文档Text Splitters&#xff0c;文档切分V…

开发实践5_后台管理^/ 分_页器

以下学习 朔宁夫 开发课 。&#xff08;Python&#xff09; 一 基本使用 创建超级用户 terminal // python manage.py createsuperuser 访问地址 //Log in | Django site adminhttp://127.0.0.1:8000/admin/login/?next/admin/ superuserr login django自带admin功能。其…

一文读懂「Prompt Engineering」提示词工程

在了解提示过程之前&#xff0c;先了解一下什么是提示prompt&#xff0c;见最后附录部分 一、什么是Prompt Engingering&#xff1f; 提示工程&#xff08;Prompt Engingering&#xff09;&#xff0c;也被称为上下文提示&#xff08;In-Context Prompting&#xff09;&#x…

Django项目中的默认文件都有什么用

manager.py&#xff1a; 是django用于管理本项目的命令行工具&#xff0c;之后进行站点运行&#xff0c;数据库自动生成等都是通过本文件完成。 djangoStudy/__init__.py&#xff1a; 告诉python该目录是一个python包&#xff0c;暂无内容&#xff0c;后期一些工具的初始化可…

【数据库】间隙锁Gap Lock

什么是间隙锁 间隙锁&#xff08;Gap Lock&#xff09;&#xff1a;间隙锁是&#xff08;RR级别下&#xff09;一个在索引记录之间的间隙上的锁&#xff0c;可以是两个索引记录之间&#xff0c;也可能是第一个索引记录之前或最后一个索引之后的空间。间隙锁&#xff08;Gap Lo…

Github登录时,要二次验证.Github 2FA登录问题

2023下半年, Github登录时,要二次验证. 点击“Enable 2FA now”跳到2FA验证界面。 点setup key是到secret串。 这里有一个可以不用app的验证方法。填入secret串,运行后,会得到验证码. https://blog.csdn.net/dejavu_980323/article/details/132318107 按照这个文章,得到验证…

分布式任务调度平台XXL-JOB使用(二)

说明&#xff1a;之前总结过在CentOS系统里使用XXL-JOB。但在代码开发阶段&#xff0c;我们有时需要在本地环境测试定时任务代码&#xff0c;本文介绍如何在Windows系统使用XXL-JOB。 下载 &#xff08;1&#xff09;下载代码&#xff0c;解压打开 首先&#xff0c;去Github…

在Android原生项目中 创建 Flutter模块

前言 应用场景&#xff1a;在已有的Android原生项目中&#xff0c;引入Flutter模块&#xff0c;摸索了两天&#xff0c;终于给整出来了&#xff1b; 如果是新项目&#xff0c;最好直接创建Flutter项目&#xff0c;然后在Fluter的 android / ios目录中&#xff0c;写原生代码&…

深思熟虑可能性模型介绍与使用

深思熟虑可能性模型介绍与使用 如何联系我 作者&#xff1a;鲁伟林 邮箱&#xff1a;thinking_fioa163.com或vlinyes163.com 版权声明&#xff1a;文章和记录为个人所有&#xff0c;如果转载或个人学习&#xff0c;需注明出处&#xff0c;不得用于商业盈利行为。 背景 20…

[牛客周赛复盘] 牛客周赛 Round 28 20240114

[牛客周赛复盘] 牛客周赛 Round 28 20240114 总结A\B1. 题目描述2. 思路分析3. 代码实现 小红的炸砖块1. 题目描述2. 思路分析3. 代码实现 小红统计区间&#xff08;easy&#xff09;1. 题目描述2. 思路分析3. 代码实现 小红的好数组1. 题目描述2. 思路分析3. 代码实现 小红统…

PPT插件-大珩助手-保留原素材的位置和大小一键替换

保留原素材的位置和大小一键替换 若勾选了一键替换&#xff0c;对于从素材库插入的图形&#xff0c;可以使得它的位置、大小与幻灯片中选中的形状一致 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持W…

在众多的材质中选择灰口铸铁铸造划线平台、铸铁平台等的原因——河北北重

使用灰口铸铁制作铸铁平台和划线平台的主要原因有以下几点&#xff1a; 强度高&#xff1a;灰口铸铁具有较高的强度和硬度&#xff0c;能够承受较大的载荷和冲击力。这使得灰口铸铁非常适合制作需要承受重压和磨损的平台和设备。 耐磨性好&#xff1a;灰口铸铁具有较高的耐磨性…

MySQL的多版本并发控制(MVCC)

MVCC MVCC&#xff0c;是Multiversion Concurrency Control的缩写&#xff0c;翻译过来是多版本并发控制&#xff0c;和数据库锁样&#xff0c;他也是一种并发控制的解决方案 我们知道&#xff0c;在数据库中&#xff0c;对数据的操作主要有2种&#xff0c;分别是读和写&…

MetaGPT学习笔记 - task1task2

章节&#xff1a;task1&task2 一.github地址&#xff1a;github.com/geekan/MetaGPT 二.MetaGPT: 多智能体框架​ 使 GPT 以软件公司的形式工作&#xff0c;协作处理更复杂的任务 MetaGPT输入一句话的老板需求&#xff0c;输出用户故事 / 竞品分析 / 需求 / 数据结构 / A…

1115: 数组最小值(数组)

题目描述 数组a有n个元素&#xff0c;请输出n个元素的最小值及其下标。若最小值有多个&#xff0c;请输出下标最小的一个。注意&#xff0c;有效下标从0开始。 输入 输入分两行&#xff0c;第一行是一个正整数n&#xff08;n<1000)&#xff0c;第二行是n个整数。 输出 …