React16源码: React中调度之scheduleWork的源码实现
scheduleWork
1 ) 概述
-
在
ReactDOM.render
,setState
,forceUpdate
这几个方法最终都调用了scheduleWork
这个方法 -
在
scheduleWork
当中,它需要去找到更新对应的FiberRoot
节点- 在使用
ReactDOM.render
的时候,传给scheduleWork
的就是FiberRoot
节点 - 在使用
setState
,forceUpdate
的时候,传过去的都是某一个组件对应的 fiber 节点,而不是 FiberRoot - 这时候需要找到
FiberRoot
节点
- 在使用
-
如果符合条件需要重置 stack
- 这个 stack 里面有一些公共的变量
- 这些公共变量在后续的调度和更新的过程中都非常的重要
- 如果符合一定的条件,要重置这个stack
-
如果符合条件就去请求工作调度
- 注意,并不是所有情况都符合这个要求,需要进行工作调度
-
拿之前的一个 Fiber Tree 节点来举例
-----current-----> FiberRoot RootFiber<---stateNode----- | |child |↓App|child|↓|div/ \/ \child child/ \/ \/ \Input-sibling-> List| \child child| \↓ \input span --sibling--> span --sibling--> span --sibling--> button
- 在上面这个 fiber tree 结构的 demo示例中,点击了最后这个 button 节点
- 实际调用 setState 是我们写的这个 List 组件
- 最终是把 RootFiber 这个 fiber 节点加入到调度队列当中
- 而不是直接把我们的 List 它对应的 fiber 对象加入到调度队列当中
- 每一次进入到调度队列的, 都是一个 RootFiber 这个对象, 不会是其他的
- 因为更新的开始,也是从 RootFiber 开始的
2 )源码
定位到 packages/react-reconciler/src/ReactFiberScheduler.js
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {const root = scheduleWorkToRoot(fiber, expirationTime);if (root === null) {return;}if (!isWorking &&nextRenderExpirationTime !== NoWork &&expirationTime < nextRenderExpirationTime) {// This is an interruption. (Used for performance tracking.)interruptedBy = fiber;resetStack();}markPendingPriorityLevel(root, expirationTime);if (// If we're in the render phase, we don't need to schedule this root// for an update, because we'll do it before we exit...!isWorking ||isCommitting ||// ...unless this is a different root than the one we're rendering.nextRoot !== root) {const rootExpirationTime = root.expirationTime;requestWork(root, rootExpirationTime);}if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {// Reset this back to zero so subsequent updates don't throw.nestedUpdateCount = 0;invariant(false,'Maximum update depth exceeded. This can happen when a ' +'component repeatedly calls setState inside ' +'componentWillUpdate or componentDidUpdate. React limits ' +'the number of nested updates to prevent infinite loops.',);}
}
- 这个方法的接收的参数
fiber: Fiber, expirationTime: ExpirationTime
- 第一个是 fiber对象
- 第二个是 expirationTime,就是在创建更新的时候去计算出来的那个过期时间
- 它一进来就调用了一个方法叫做
scheduleWorkToRoot
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {recordScheduleUpdate();if (__DEV__) {if (fiber.tag === ClassComponent) {const instance = fiber.stateNode;warnAboutInvalidUpdates(instance);}}// Update the source fiber's expiration timeif (fiber.expirationTime === NoWork ||fiber.expirationTime > expirationTime) {fiber.expirationTime = expirationTime;}let alternate = fiber.alternate;if (alternate !== null &&(alternate.expirationTime === NoWork ||alternate.expirationTime > expirationTime)) {alternate.expirationTime = expirationTime;}// Walk the parent path to the root and update the child expiration time.let node = fiber.return;let root = null;if (node === null && fiber.tag === HostRoot) {root = fiber.stateNode;} else {while (node !== null) {alternate = node.alternate;if (node.childExpirationTime === NoWork ||node.childExpirationTime > expirationTime) {node.childExpirationTime = expirationTime;if (alternate !== null &&(alternate.childExpirationTime === NoWork ||alternate.childExpirationTime > expirationTime)) {alternate.childExpirationTime = expirationTime;}} else if (alternate !== null &&(alternate.childExpirationTime === NoWork ||alternate.childExpirationTime > expirationTime)) {alternate.childExpirationTime = expirationTime;}if (node.return === null && node.tag === HostRoot) {root = node.stateNode;break;}node = node.return;}}if (root === null) {if (__DEV__ && fiber.tag === ClassComponent) {warnAboutUpdateOnUnmounted(fiber);}return null;}if (enableSchedulerTracing) {const interactions = __interactionsRef.current;if (interactions.size > 0) {const pendingInteractionMap = root.pendingInteractionMap;const pendingInteractions = pendingInteractionMap.get(expirationTime);if (pendingInteractions != null) {interactions.forEach(interaction => {if (!pendingInteractions.has(interaction)) {// Update the pending async work count for previously unscheduled interaction.interaction.__count++;}pendingInteractions.add(interaction);});} else {pendingInteractionMap.set(expirationTime, new Set(interactions));// Update the pending async work count for the current interactions.interactions.forEach(interaction => {interaction.__count++;});}const subscriber = __subscriberRef.current;if (subscriber !== null) {const threadID = computeThreadID(expirationTime,root.interactionThreadID,);subscriber.onWorkScheduled(interactions, threadID);}}}return root; }
- 上面首先调用了
recordScheduleUpdate
这个方法是react当中的用来记录更新流程当中的时间 - 这里涉及的东西比较多,先跳过,涉及一个独立的模块
- DEV 的判断也跳过
- 在这个方法中
- 根据传入进来的 fiber 节点,它要去向上寻找,找到对应的 RootFiber 对象
- 找的过程当中,进行一系列的操作
if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime)
- 当这个节点没有任何更新(没有设置过过期时间) 或者 已有更新产生未完成的任务的优先级 低于 当前的更新
- 则将本身设置成优先级更高的 expirationTime
- 这里主要任务是更新优先级
- 第二步获取
alternate
并进行判断if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime))
- 这很明显就是用来更新
alternate
的 expirationTime - 它其实跟上面更新
fiber.expirationTime
是一样的
- 这很明显就是用来更新
- 接下去,往上一层去找,比如 List 这个组件的 return 属性是 上一层的 div 组件
- 进入判断
if (node === null && fiber.tag === HostRoot)
- 注意,只有
RootFiber.return === null
, 其他的都有上级Fiber - 当
node === null
时,当前 fiber 就是 RootFiber, 同时,HostRoot
代表的就是RootFiber
- 这时候,
root = fiber.stateNode
- 注意,只有
- 如果不满足上述判断,则进入循环处理
- whle循环,向上查找
node.childExpirationTime
是否是子树中优先级最高的- 如果不是高优先级,则更新成高优先级的
- 同样对
alternate.childExpirationTime
的判断也是同样的更新 - 接着,对
node.return
和node.tag
的判断是否是 RootFiber, 赋值 root - 如果不是,则
node = node.return
向上查找
- whle循环,向上查找
- 如果
root === null
的时候,则return null
- 下面的
if (enableSchedulerracing)
不涉及主流程,跳过 - 最终 return root, 把 FiberRoot 返回
- 上面首先调用了
- 获取 root 后,如果 root 是 null,则 return
- 接着
if(!isWorking && nextRenderExpiration !== NoWork && expirationTime < nextRenderExpirationTime)
- 如果没有在渲染更新的任务,并且,任务是异步任务没有执行完,并且新任务的优先级高于nextRenderExpirationTime 优先级
- 这个意思是 新的高优先级任务打断了老的低优先级的任务,这是一个非常重要的特性
- 它让我们可以优先执行高优先级的任务
interruptedBy
是一个被用来记录在哪里打断的变量值,这里可以不用太关注- 进入
resetStack()
function resetStack() {// nextUnitOfWork 用来记录遍历整个子树的时候,执行到了哪个节点的更新, 即下一个即将要更新的节点// 这个判断如果是 true 则代表,之前的更新的是一个异步的任务,执行到一部分,由于时间片不够,把执行权交给浏览器// 这个值记录了下一个要执行的节点,如果现在没有优先级任务中断,它会回来继续执行 nextUnitOfWork 的任务更新if (nextUnitOfWork !== null) {// 指向上级,向上查找let interruptedWork = nextUnitOfWork.return;// 不断向上找被打断的任务,执行 unwindInterruptedWorkwhile (interruptedWork !== null) {// 有了更高优先级的任务进来了,要进行更新,还是要从头开始更新,有存在的 nextUnitOfWork// 说明上层的几个组件 可能已经进行过更新了, 新的任务进来,再从头进行更新可能会导致前后的state并非这次想要更新的state// 会导致 state 错乱,在这里有 nextUnitOfWork 的情况,要把之前已经更新过的节点(上一个优先级任务对应的节点) 进行状态退回// 把状态退回到没有更新过的状态,再去执行优先级更高的任务,这块具体的先跳过unwindInterruptedWork(interruptedWork);interruptedWork = interruptedWork.return;}}if (__DEV__) {ReactStrictModeWarnings.discardPendingWarnings();checkThatStackIsEmpty();}// 设置公共变量的重置nextRoot = null;nextRenderExpirationTime = NoWork;nextLatestAbsoluteTimeoutMs = -1;nextRenderDidError = false;nextUnitOfWork = null; }
- 接着,
markPendingPriorityLevel
- 这部分先跳过,涉及流程较多
- 接着进入判断,
if(!isWorking || isCommitting || nextRoot !== root)
是否要requestWork
- 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
- 注意,isWorking 会包含 committing
- committing 是第二阶段,是不可打断的阶段,把fiber树渲染完成后,更新到dom上的这个过程是 commiting 阶段
- isCommitting 只有在中间执行的过程中才会是 true, 其他才会是false, 可以查看
commitRoot
函数
- 符合条件,赋值
rootExpirationTime
并调用 requestWork 函数- 为何要重新读取 expirationTime, 因为之前调用过
markPendingPriorityLevel
可能导致 - root.epirationTime 不一定和传入的 expirationTime 是一样的
- 为何要重新读取 expirationTime, 因为之前调用过
- 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
- 最后是涉及到 nestUpdateCount 的判断
- 用来防止在组件更新的流程中,比如在 componentDidMount 中再次修改状态
- 导致又进行了一次更新, 导致无限循环的处理,这里进行提醒
相关文章:
React16源码: React中调度之scheduleWork的源码实现
scheduleWork 1 ) 概述 在 ReactDOM.render, setState, forceUpdate 这几个方法最终都调用了 scheduleWork 这个方法 在 scheduleWork 当中,它需要去找到更新对应的 FiberRoot 节点 在使用 ReactDOM.render 的时候,传给 scheduleWork 的就是…...

【STM32】| 02——常用外设 | I2C
系列文章目录 【STM32】| 01——常用外设 | USART 【STM32】| 02——常用外设 | I2C 失败了也挺可爱,成功了就超帅。 文章目录 前言1. 简介2. I2C协议2.1 I2C物理连接2.2 I2C通信协议2.2.1 起始和停止信号2.2.2 数据有效性2.2.3 数据传输格式2.2.4 从机地址/数据方…...

微服务架构设计核心理论:掌握微服务设计精髓
文章目录 一、微服务与服务治理1、概述2、Two Pizza原则和微服务团队3、主链路规划4、服务治理和微服务生命周期5、微服务架构的网络层搭建6、微服务架构的部署结构7、面试题 二、配置中心1、为什么要配置中心2、配置中心高可用思考 三、服务监控1、业务埋点的技术选型2、用户行…...
.net core 6 集成和使用 mongodb
1、安装包 MongoDB.Driver 2、定义顶层类 /// <summary> /// monggodb规范 /// </summary> public abstract class MongoDBToolBase { /// <summary> /// 客户端 /// </summary> protected MongoClient mongoClient { get; private …...

07-微服务getaway网关详解
一、初识网关 在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。这样的话会产生很多问题,例…...

MS2660:L1 频段卫星导航射频前端低噪声放大器芯片
MS2660 是一款具有高增益、低噪声系数的低噪声放 大器(LNA)芯片,支持 L1 频段多模式全球卫星定位,可 以应用于 GPS、北斗二代、伽利略、Glonass 等 GNSS 导航 接收机中。芯片采用先进工艺制造,封装采用 2 mm 2 mm …...

微信小程序防止截屏录屏
一、使用css添加水印 使用微信小程序原生的view和css给屏幕添加水印这样可以防止用户将小程序内的隐私数据进行截图或者录屏分享导致信息泄露,给小程序添加一个水印浮层。这样即使被截图或者拍照,也能轻松地确定泄露的源头。效果图如下: 代码…...

126.(leaflet篇)leaflet松散型arcgis缓存切片加载
地图之家总目录(订阅之前必须详细了解该博客) arcgis缓存切片数据格式如下: 完整代码工程包下载,运行如有问题,可“私信”博主。效果如下所示: leaflet松散型arcgis缓存切片加载 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYP...

物联网介绍
阅读引言: 本文从多方面叙述物联网的定义以及在物联网当中的各种通信的介绍。 一、物联网的定义 1.1 通用的定义 物联网(Internet of Things,IOT;也称为Web of Things)是指通过各种信息传感设 备,如传感器、…...

Flume 之自定义Sink
1、简介 前文我们介绍了 Flume 如何自定义 Source, 并进行案例演示,本文将接着前文,自定义Sink,在这篇文章中,将使用自定义 Source 和 自定义的 Sink 实现数据传输,让大家快速掌握Flume这门技术。 2、自定…...

【1】SM4 CBC-MAC 机制
0x01 题目 MSG1: e55e3e24a3ae7797808fdca05a16ac15eb5fa2e6185c23a814a35ba32b4637c2 MAC1: 0712c867aa6ec7c1bb2b66312367b2c8 ----------------------------------------------------- MSG2: d8d94f33797e1f41cab9217793b2d0f02b93d46c2ead104dce4bfec453767719 MAC2: 4366…...
响应式编程Reactor API大全(下)
Reactor 是一个基于响应式编程的库,主要用于构建异步和事件驱动的应用程序。Reactor 提供了丰富的 API,包括创建、转换、过滤、组合等操作符,用于处理异步数据流。以下是一些 Reactor 的主要 API 示例: pom依赖 <dependencyMan…...

【STM32】HAL库的STOP低功耗模式UART串口唤醒,解决首字节出错的问题(全网第一解决方案)
【STM32】HAL库的STOP低功耗模式UART串口唤醒,解决首字节出错的问题(全网第一解决方案) 前文: 【STM32】HAL库的STOP低功耗模式UART串口唤醒,第一个接收字节出错的问题(疑难杂症) 目前已解决 …...
Python 语法糖
一、基本概念 语法糖,可以理解为:“甜蜜” 的便捷语法。 它是编程语言为程序提供的更简洁、更易读的语法实现的语法结构,它并不影响语言的功能,仅仅是一种更便捷的书写方式。 这就像你制作蛋糕时,使用现代烤箱而不是…...

一个小程序跳转到另一个小程序中如何实现
小程序 保证两个小程序是一样的主体才可以跳转。怎么知道是不是同样的主体呢? 小程序的后台管理-设置-基本设置-基本信息。查看主体信息。 跳转 <button clicktoOtherMini()>跳转到另一个小程序</button> function toOtherMini(){wx.navigateToMini…...

STM32+HAL库驱动ADXL345传感器(SPI协议)
STM32HAL库驱动ADXL345传感器(SPI协议) ADXL345传感器简介实物STM32CubeMX配置SPI配置片选引脚配置串口配置 特别注意(重点部分)核心代码效果展示 ADXL345传感器简介 ADXL345 是 ADI 公司推出的基于 iMEMS 技术的 3 轴、数字输出加…...

Redis实现全局唯一Id
一、全局唯一ID 每个店铺都可以发布优惠券: 当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题: id的规律性太明显 受单表数据量的限制 场景分析:如果我们的…...

【J-Flash基本使用总结】
【J-Flash基本使用总结】 VX:hao541022348 ■ 烧录文件■ 创建新的工程■ 烧录模式-SWD模式■ J-Flash下载程序到单片机 ■ J-Flash拼接多个hex或bin文件■ J-Flash读单片机的option byte■ J-Flash读单片机Flash数据■ 将读出来的文件用jflash烧录到其他的芯片■ 设…...

宝塔发布网站问题汇总和记录
1、添加网站站点后打不开 解决办法,关闭防跨站攻击2 2、laravel项目部署到linux的时候出现The stream or file "/home/www/storage/logs/laravel.log" could not be opened in append mode 给目录加权限 chmod -R 777 storage 3、Class "Redis"…...

决战排序之巅(二)
决战排序之巅(二) 排序测试函数 void verify(int* arr, int n) 归并排序递归方案代码可行性测试 非递归方案代码可行性测试 特点分析 计数排序代码实现代码可行性测试 特点分析 归并排序 VS 计数排序(Release版本)说明1w rand( ) …...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...