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.returnnode.tag 的判断是否是 RootFiber, 赋值 root
          • 如果不是,则 node = node.return 向上查找
      • 如果 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 是一样的
  • 最后是涉及到 nestUpdateCount 的判断
    • 用来防止在组件更新的流程中,比如在 componentDidMount 中再次修改状态
    • 导致又进行了一次更新, 导致无限循环的处理,这里进行提醒

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

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

相关文章

RocketMQ源码阅读-Producer消息发送

RocketMQ源码阅读-Producer消息发送 1. 从单元测试入手2. 启动过程3. 同步消息发送过程4. 异步消息发送过程5. 小结 Producer是消息的生产者。 Producer和Consummer对Rocket来说都是Client&#xff0c;Server是Broker。 客户端在源码中是一个单独的Model&#xff0c;目录为rock…

ASP.NET Core 的 Web Api 实现限流 中间件

Microsoft.AspNetCore.RateLimiting 中间件提供速率限制&#xff08;限流&#xff09;中间件。 它是.NET 7 以上版本才支持的中间件&#xff0c;刚看了一下&#xff0c;确实挺好用&#xff0c;下面给大家简单介绍一下&#xff1a; RateLimiterOptionsExtensions 类提供下列用…

本地浏览器查看--服务器上运行的可视化,如tensorboard

特别提醒&#xff1a;注意看本地端和服务器端 一、服务器终端运行 tensorboard --logdirlogs --port6008二、本地终端运行--以建立端口映射 ssh -L 6008:localhost:6008 -p 443 root服务器IP解释&#xff1a;将服务器的6008端口映射到本地的6008端口&#xff0c;-p 443 …

收支明细曲线图:一图掌握你的财务变化趋势!

想要快速了解你的收支明细和变化趋势吗&#xff1f;不需要复杂的财务表格&#xff0c;一个曲线图就能让你一目了然&#xff01;现在&#xff0c;就让我们带你走进「图形化分析收支变化趋势」的世界&#xff0c;让你轻松掌握自己的财务状况。 首先&#xff0c;第一步&#xff0…

超结MOS在舞台灯电源上的应用-REASUNOS瑞森半导体

一、前言 舞台灯电源是一种为舞台灯具提供电力转换和控制的设备&#xff0c;它可以根据不同的灯具类型和需求&#xff0c;提供恒流或恒压、可调光或不可调光、模拟或数字或网络等输出模式。 舞台灯电源的主要特点是具有高效、稳定、安全、智能等功能&#xff0c;它可以适应不…

MySQL表状态参数解读

查看表状态 SHOW TABLE STATUS where name ***; 返回结果示例 *************************** 1. row ***************************Name: ***Engine: InnoDBVersion: 10Row_format: CompactRows: 2036375Avg_row_length: 139Data_length: 284164096 Max_data_length: 0Index_…

ARCH使用率100% ,asmcmd显示无文件

oracle rac 11.2.0.3 归档满了之后,无法连接数据库,通过ASMCMD删除文件之后,还是无法连接 原归档ARCH盘已没有使用,但是空间还是持续增长到100%,手动切换归档正常,但是日志偶尔会出现无法归档的报错 ARCH使用率100% ,asmcmd连接显示里面没有文件 ,无法清理 ,应用偶尔出现连接超…

一台电脑如何通过另一台联网电脑访问网络

电脑A没有连接网络&#xff0c;电脑B已经连接wifi。 电脑A如何通过访问电脑B从而连接网络&#xff1f; 1. 将这2台电脑用网线直连 2. 电脑B打开【网络和Internet设置】 3. 右键点击WLAN&#xff0c;选择属性&#xff0c;进入共享tab页面&#xff0c;勾选【允许其他网络用户通过…

Kafka集群与可靠性

Kafka集群与可靠性 1.Kafka集群搭建实战 使用两台Linux服务器&#xff1a;一台192.168.182.137 一台192.168.182.138 安装kafka首先&#xff0c;我们需要配置java环境变量&#xff08;这里就略过了&#xff09; mkdir /opt/kafka #上传压缩包kafka_2.13-3.3.1.tgz并解压 ta…

Rust-Panic

什么是panic 在Rust中&#xff0c;有一类错误叫作panic。示例如下&#xff1a; 编译&#xff0c;没有错误&#xff0c;执行这段程序&#xff0c;输出为&#xff1a; 这种情况就引发了一个panic。在这段代码中&#xff0c;我们调用了Option::unwrap()方法&#xff0c;正是这个方…

SparkSQL初体验

SparkSQL初体验 命令式的 API RDD 版本的 WordCount val conf new SparkConf().setAppName("ip_ana").setMaster("local[6]") val sc new SparkContext(conf)sc.textFile("hdfs://master:9000/dataset/wordcount.txt").flatMap(_.split("…

设计一个抽奖系统

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

复试 || 就业day16(2024.01.16)算法篇

文章目录 前言环和杆检查是否每一行每一列都包含全部整数将找到的值乘以 2数组中紧跟 key 之后出现最频繁的数字将数组划分成相等数对找出两数组的不同多个数组求交集移除字母异位词后的结果数组* 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考研复…

在uniapp Vue3版本中如何解决web/H5网页浏览器跨域的问题

问题复现 uniapp项目在浏览器运行&#xff0c;有可能调用某些接口会出现跨域问题&#xff0c;报错如下图所示&#xff1a; 什么是跨域&#xff1f; 存在跨域问题的原因是因为浏览器的同源策略&#xff0c;也就是说前端无法直接发起跨域请求。同源策略是一个基础的安全策略&a…

前端下载文件流,设置返回值类型responseType:‘blob‘无效的问题

前言&#xff1a; 本是一个非常简单的请求&#xff0c;即是下载文件。通常的做法如下&#xff1a; 1.前端通过Vue Axios向后端请求&#xff0c;同时在请求中设置响应体为Blob格式。 2.后端相应前端的请求&#xff0c;同时返回Blob格式的文件给到前端&#xff08;如果没有步骤…

shell脚本 $0-$n $* $@ $# $? $$

各命令详解 1.$0-$n &#xff1a;表示脚本或函数的参数。$0 是脚本的名称&#xff0c;$1 到 $n 是位置参数&#xff0c;每个对应一个传递给脚本或函数的参数。 2.$* &#xff1a;表示所有传递给脚本或函数的参数。它将所有位置参数作为单个字符串显示。 3.$ &#xff1a;表示所…

时序预测 | MATLAB实现GRNN广义回归神经网络时间序列未来多步预测(程序含详细预测步骤)

时序预测 | MATLAB实现GRNN广义回归神经网络时间序列未来多步预测(程序含详细预测步骤) 目录 时序预测 | MATLAB实现GRNN广义回归神经网络时间序列未来多步预测(程序含详细预测步骤)预测效果基本介绍程序设计参考资料预测效果 基本介绍 MATLAB实现GRNN广义回归神经网络时间序列…

大语言模型面试问题【持续更新中】

自己在看面经中遇到的一些面试题&#xff0c;结合自己和理解进行了一下整理。 transformer中求和与归一化中“求和”是什么意思&#xff1f; 求和的意思就是残差层求和&#xff0c;原本的等式为y H(x)转化为y x H(x)&#xff0c;这样做的目的是防止网络层数的加深而造成的梯…

openssl3.2 - 官方demo学习 - pkey - EVP_PKEY_DSA_paramgen.c

文章目录 openssl3.2 - 官方demo学习 - pkey - EVP_PKEY_DSA_paramgen.c概述笔记END openssl3.2 - 官方demo学习 - pkey - EVP_PKEY_DSA_paramgen.c 概述 产生DSA的_evp_pkey_ctx 初始化_evp_pkey_ctx 设置参数到_evp_pkey_ctx 由_evp_pkey_ctx产生_evp_pkey 打印_evp_pkey公…

Angular系列教程之观察者模式和RxJS

文章目录 引言RxJS简介RxJS中的设计模式观察者模式迭代器模式 示例代码RxJS 在 Angular 中的应用总结 引言 在Angular开发中&#xff0c;我们经常需要处理异步操作&#xff0c;例如从后端获取数据或与用户的交互。为了更好地管理这些异步操作&#xff0c;Angular中引入了RxJS&…