React18源码: Fiber树中的优先级与帧栈模型
优先级{#lanes}
- 在全局变量中有不少变量都以Lanes命名
- 如workInProgressRootRenderLanes, subtreeRenderLanes其作用见上文注释
- 它们都与优先级相关
- React中有3套优先级体系,并了解了它们之间的关联关系
- 现在来看下fiber树构造过程中,车道模型Lane的具体应用
- 在整个react-reconciler包中,Lane的应用可以分为3个方面:
1 ) update优先级(update.lane){#update-lane}
-
update对象,它是一个环形链表.
-
对于单个update对象来讲,update.lane代表它的优先级,称之为update优先
-
观察其构造函数, 其优先级是由外界传入
export function createUpdate(eventTime: number, lane: Lane): Update<*> {const update: Update<*> = {eventTime,lane,tag: UpdateState,payload: null,callback: null,next: null,}return update; } -
在React体系中,有2种情况会创建update对象:
-
1.应用初始化:在react-reconciler包中的updateContainer函数中
export function updateContainer(element: ReactNodelist,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,callback: ?Function, ):Lane {const current = container.current;const eventTime = requestEventTime();const lane = requestUpdateLane(current); //根据当前时间,创建一个update优先级const update = createUpdate(eventTime, lane); //lane被用于创建update对象update.payload = { element };enqueueUpdate(current, update);scheduleUpdateOnFiber(current, lane, eventTime);return lane; } -
发起组件更新: 假设在 class 组件中调用 setState
const classComponentUpdater = {isMounted,enqueueSetState(inst, payload, callback) {const fiber = getInstance(inst);const eventTime = requestEventTime(); // 根据当前时间,创建一个update优先级const lane = requestUpdateLane(fiber); // lane被用于创建update对象const update = createUpdate(eventTime, lane);update.payload = payload;enqueueUpdate(fiber, update);scheduleUpdateOnFiber(fiber, lane, eventTime);}, }; -
可以看到,无论是应用初始化或者发起组件更新,创建update.lane的逻辑都是一样的
-
都是根据当前时间,创建一个update优先级
export function requestUpdateLane(fiber: Fiber): Lane {// Special casesconst mode = fiber. mode;if ((mode & BlockingMode) === NoMode) {// legacy 模式return (SyncLane: Lane);} else if ((mode & ConcurrentMode) === NoMode) {// blocking 模式return getCurrentPrioritylevel() === ImmediateSchedulerPriority? (Synclane: Lane): (SyncBatchedLane: Lane);}//concurrent模式if (currentEventWipLanes === NoLanes) {currentEventWipLanes = workInProgressRootIncludedLanes;}const isTransition = requestCurrentTransition() !== NoTransition;if(isTransition) {// 特殊情况,处于suspense过程中if (currentEventPendingLanes !== NoLanes) {currentEventPendingLanes =mostRecentlyUpdatedRoot !== null? mostRecentlyUpdatedRoot. pendingLanes: NoLanes;}return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);}// 正常情况,获取调度优先级const schedulerPriority = getCurrentPriorityLevel();let lane;if ((executionContext & DiscreteEventContext) !== NoContext &&schedulerPriority === UserBlockingSchedulerPriority){// executionContext 存在输入事件,且调度优先级是用户阻塞性质lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);} else {// 调度优先级转换为车道模型const schedulerLanePriority = schedulerPriorityToLanePriority(schedulerPriority,);lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);}return lane; } -
可以看到requestUpdateLane的作用是返回一个合适的update优先级.
- 1.legacy模:返回SyncLane
- 2.blocking模式:返回SyncLane
- 3.concurrent模式:
- 正常情况下,根据当前的调度优先级来生成一个1ane.
- 特殊情况下(处于suspense过程中),会优先选择TransitionLanes通道中的空闲通道
- 如果所有TransitionLanes通道都被占用,就取最高优先级
-
最后通过
scheduleUpdateOnFiber(current, lane, eventTime);函数 -
把 update.lane正式带入到了输入阶段
-
scheduleUpdateOnFiber是输入阶段的必经函数,此处以 update.lane 的视角分析:export function scheduleUpdateOnFiber(fiber: Fiber,lane:Lane,eventTime: number, ) {if(lane === SyncLane) {// legacyblocking模式if ((executionContext & LegacyUnbatchedContext) !== NoContext &&(executionContext & (RenderContext CommitContext)) === NoContext) {performSyncWorkOnRoot(root);} else {ensureRootIsScheduled(root,eventTime); // 注册回调任务if (executionContext === NoContext) {flushSyncCallbackQueue(); // 取消schedule调度,主动刷新回调队列,}}} else {// concurrent模式ensureRootIsScheduled(root, eventTime);} } -
当lane==SyncLane也就是legacy或blocking模式中,注册完回调任务之后
-
(ensureRootIsScheduled(root, eventTime)),如果执行上下文为空
-
会取消schedule调度,主动刷新回调队列flushsyncCallbackQueue()
-
这里包含了一个热点问题(setState到底是同步还是异步)的标准答案:
- 如果逻辑进入 flushSyncCallbackQueue(executionContext == NoContext)
- 则会主动取消调度,并刷新回调,立即进入fiber树构造过程
- 当执行setState下一行代码时,fiber树已经重新渲染了,故setState体现为同步
- 正常情况下,不会取消schedule调度
- 由于schedule调度是通过MessageChannel触发(宏任务),故体现为异步
- 如果逻辑进入 flushSyncCallbackQueue(executionContext == NoContext)
2 ) 渲染优先级(renderLanes)
-
这是一个全局概念,每一次render之前,首先要确定本次render的优先级,具体对应到源码如下:
//...省略无关代码 function performSyncWorkOnRoot(root) {let lanes;let exitStatus;//获取本次`render'的优先级lanes = getNextLanes(root, lanes);exitStatus = renderRootSync(root, lanes); } //...省略无关代码 function performConcurrentWorkOnRoot(root) {//获取本次`render`的优先级let lanes = getNextLanes(root,root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,);if (lanes === NoLanes){return null;}let exitStatus = renderRootConcurrent(root, lanes); } -
可以看到,无论是 Legacy 还是 Concurrent 模式,在正式 render 之前,都会调用 getNextLanes 获取一个优先级
//...省略部分代码 export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {// 1. check是否有等待中的lanesconst pendingLanes = root.pendingLanes;if (pendinglanes === NoLanes) {return_highestLanePriority = NoLanePriority;return NoLanes;}let nextLanes = NoLanes;let nextLanePriority = NoLanePriority;const expiredLanes = root.expiredLanes;const suspendedLanes = root.suspendedLanes;const pingedLanes = root.pingedLanes;// 2.check是否有已过期的Lanesif (expiredlanes !== NoLanes) {nextLanes = expiredLanes;nextlanePriority = return_highestLanePriority = SynclanePriority;} else {const nonIdlePendingLanes = pendingLanes & NonIdleLanes;if (nonIdlePendingLanes !== NoLanes) {//非Idle任务...} else {//Idle任务...}}if (nextLanes == NoLanes) {return NoLanes;}return nextLanes; } -
getNextLanes 会根据 fiberRoot 对象上的属性(expiredLanes, suspendedLanes, pingedLanes等)
-
确定出当前最紧急的1anes
-
此处返回的lanes会作为全局渲染的优先级,用于fiber树构造过程中
-
针对fiber对象或update对象,只要它们的优先级(如:fiber.lanes和update.lane)比渲染优先级低,都将会被忽略
3 ) fiber优先级(fiber.lanes)
- 介绍过fiber对象的数据结构.其中有2个属性与优先级相关:
-
1.fiber.lanes
- 代表本节点的优先级
-
2.fiber.childLanes
- 代表子节点的优先级从FiberNode的构造函数中可以看出
- fiber.lanes 和 fiber.childLanes的初始值都为NoLanes
- 在fiber树构造过程中,使用全局的渲染优先级 ( renderLanes)和 fiber.lanes 判断 fiber 节点是否更新.
- 如果全局的渲染优先级rendertanes不包括fiber.lanes
- 证明该fiber节点没有更新,可以复用.
- 如果不能复用,进入创建阶段
function beginWork(current: Fiber| null,workInProgress: Fiber,renderLanes: Lanes, ): Fiber | null {const updatelanes = workInProgress.lanes;if(current !== null) {const oldProps = current.memoizedProps;const newProps = workInProgress.pendingProps;if(oldProps !== newProps ||hasLegacyContextChanged()// Force a re-render if the implementation changed due to hot reload:(_DEV__ ? workInProgress.type !== current. type : false)) {didReceiveUpdate = true;} else if (!includesSomeLane(renderLanes, updateLanes)) {didReceiveUpdate = false;//本`fiber`节点的没有更新,可以复用,进入bailout逻辑return bailoutOnAlreadyFinishedwork(current, workInProgress, renderlanes);}}// 不能复用,创建新的fiber节点workInProgress.lanes = NoLanes;//重优为 NoLanesswitch (workInProgress.tag) {case ClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress. pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateClassComponent(current,workInProgress,Component,resolvedProps,// 正常情况下渲染优先级会被用于fier树的构透过程renderLanes,);}} }
-
栈帧管理
-
在React源码中,每一次执行fiber树构造
- 也就是调用performSyncWorkOnRoot或者performConcurrentWorkOnRoot函数的过程
- 都需要一些全局变量来保存状态
-
如果从单个变量来看,它们就是一个个的全局变量.
-
如果将这些全局变量组合起来,它们代表了当前fiber树构造的活动记录
-
通过这一组全局变量,可以还原fiber树构造过程
-
比如时间切片的实现过程fiber树构造过程被打断之后需要还原进度,全靠这一组全局变量
-
所以每次fiber树构造是一个独立的过程,需要独立的一组全局变量
-
在React内部把这一个独立的过程封装为一个栈帧stack
-
简单来说就是每次构造都需要独立的空间
-
所以在进行fiber树构造之前,如果不需要恢复上一次构造进度,都会刷新栈帧(源码在prepareFreshStack函数)
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {const prevExecutionContext = executionContext;executionContext |= RenderContext;const prevDispatcher = pushDispatcher();// 如果fiberRoot变动,或者update.lane变动,都会刷新栈帧,丢弃上一次渲染进度if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {resetRenderTimer();// 刷新找帧prepareFreshStack(root, lanes);startWorkOnPendingInteractions(root, lanes);} }// 刷新栈帧;重置FiberRoot上的全局属性和`fiber树构造'循环过程中的全局变量 function prepareFreshStack(root: FiberRoot, lanes: Lanes) {// 重置FiberRoot对象上的属性root.finishedWork = null;root.finishedLanes = NoLanes;const timeoutHandle = root.timeoutHandle;if(timeoutHandle !== noTimeout) {root.timeoutHandle = noTimeout;cancelTimeout(timeoutHandle);}if (workInProgress !== null) {let interruptedWork = workInProgress.return;while (interruptedWork !== null){unwindInterruptedWork(interruptedWork);interruptedWork =interruptedWork.return;}}// 重置全局变量workInProgressRoot = root;workInProgress = createWorkInProgress(root.current, null); // 给HostRootFiber对象创建一个alternateworkInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = laneworkInProgressRootExitStatus = RootIncomplete;workInProgressRootFatalError = null;workInProgressRootSkippedlanes = NoLanes; } -
注意其中的
createWorkInProgress(root.current, null) -
其参数
root.current即HostRootFiber -
作用是给
HostRootFiber创建一个 alternate副本 -
workInProgress 指针指向这个副本, 即
workInProgress = HostRootFiber.alternate -
在前文 double buffering 中分析过,HostRootFiber.alternate 是正在构造的fiber树的根节点
相关文章:
React18源码: Fiber树中的优先级与帧栈模型
优先级{#lanes} 在全局变量中有不少变量都以Lanes命名 如workInProgressRootRenderLanes, subtreeRenderLanes其作用见上文注释它们都与优先级相关 React中有3套优先级体系,并了解了它们之间的关联关系现在来看下fiber树构造过程中,车道模型Lane的具体应…...
Hive 最全面试题及答案(基础篇)
基本知识 hive元数据存储 Hive 元数据存储了关于表、分区、列、分桶等信息。 在生产环境中,通常会将 Hive 的元数据存储在外部的关系型数据库中,如 MySQL 或 PostgreSQL。这样可以提供更好的性能、可扩展性和容错性。通过配置 Hive 的元数据存储为 MySQL 或 PostgreSQL,可以…...
【力扣】整数反转,判断是否溢出的数学解法
整数反转原题地址 方法一:数学 反转整数 如何反转一个整数呢?考虑整数操作的3个技巧: xmod10 可以取出 x 的最低位,如 x123 , xmod103 。x/10 可以去掉 x 的最低位,如 x123 , x/10 …...
Jmeter之内置函数__property和__P的区别
1. __property函数 作用 读取 Jmeter 属性 语法格式 ${__property(key,var,default)} 参数讲解 小栗子 ${__property(key)} 读取 key 属性如果找不到 key 属性,则返回 key(属性名) ${__property(key,,default)} 读取 key 属性如果找不到 k…...
GPT润色指令
1. GPT润色指令 Below is a paragraph from an academic paper. Polish the writing to meet the academic style,improve the spelling, grammar, clarity, concision and overall readability. When necessary, rewrite the whole sentence. Paragraph :你的句子…...
Ubuntu中matplotlib显示中文的方法
其实有很多朋友已经总结得很好了:Ubuntu下让matplotlib显示中文字体_ubuntu matplot 使用汉字-CSDN博客 这里我就是简单补充一下: 按照上面这篇博客,下载:GitHub - tracyone/program_font: fonts for programmer 然后运行&#…...
String类-equals和==的区别-遍历-SubString()-StringBuilder-StringJoiner-打乱字符串
概述 String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候…...
IDEA的LeetCode插件的设置
一、下载插件 选择点击File->Setting->Plugins:搜索LeetCode 二、打开这个插件 选择View —>Tool Windows—>leetcode 三、登陆自己的账号 关于下面几个参数的定义,官方给的是: Custom code template: 开启使用自定义模板&…...
2024.2.29 模拟实现 RabbitMQ —— 项目展示
目录 项目介绍 核心功能 核心技术 演示直接交换机 演示扇出交换机 演示主题交换机 项目介绍 此处我们模拟 RabbitMQ 实现了一个消息队列服务器 核心功能 提供了 虚拟主机、交换机、队列、绑定、消息 概念的管理九大核心 API 创建队列、销毁队列、创建交换机、销毁交换机、…...
React htmlfor
注意,在添加属性时, class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。 在React中,当我们需要为一个表单元素设置标签时,可以使用htmlFor属性。它…...
现代化数据架构升级:毫末智行自动驾驶如何应对年增20PB的数据规模挑战?
毫末智行是一家致力于自动驾驶的人工智能技术公司,其前身是长城汽车智能驾驶前瞻分部,以零事故、零拥堵、自由出行和高效物流为目标,助力合作伙伴重塑和全面升级整个社会的出行及物流方式。 在自动驾驶领域中,是什么原因让毫末智行…...
理解Stable Diffusion、LoRA、Dreambooth、Hypernetworks、Textual Inversion、Checkpoint
前言 在深度学习和人工智能的领域中,模型生成和调整技术的快速发展为创造性内容的自动化提供了新的可能性。本文将介绍四种重要的模型技术——Stable Diffusion、LoRA、Dreambooth、和Hypernetworks——它们在生成艺术、个性化模型调整和网络结构设计方面各自的特点…...
spring boot3登录开发-2(1图形验证码接口实现)
⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途。 目录 前置条件 内容简介 图形验证码接口实现 导入糊涂工具依赖 接口分析 编写验证码接口 测试验证码接口 前置条件 …...
网络编程中的问题总结
1、服务端重启后bind失败,因为TCP 套接字状态 TIME_WAIT 引起,该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。可以通过setsockopt()设置Socket描述符的选项S…...
数据结构-关键路径
介绍 在AOV网的基础上,如果用对应边来表示活动持续时间,这种有向图被称为AOE网在AOE网中,入度为0的为源点,出度为0的为汇点,整张网看做是一件事情完成的过程,那么这两个点就是事情的开始和结束。每个活动持…...
进程间通信学习笔记(共享内存)
内存映射概念: 共享内存可以通过mmap()映射普通文件使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通文件一样对文件进行访问,不必再强调read,write。 mmap的优点: 实现了用户空间和内核空间的高效交互方式 mmap的…...
ChatGPT学习第三周
📖 学习目标 ChatGPT在各行各业的应用 探索ChatGPT在不同领域(如教育、客户服务等)的实际应用案例。 ChatGPT的局限性和挑战 讨论ChatGPT面临的挑战,包括偏见、误解及其限制。 ✍️ 学习活动 学习资料 《人工智能通用大模型(…...
R语言混合效应(多水平/层次/嵌套)模型及贝叶斯实现技术应用
回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展,回归分析方法得到了极大改进。混合效应模型(Mixed effect model),即多水平模(Multilevel model)/分层模型(Hierarchical Model)/嵌套模…...
[C++]使用C++部署yolov9的tensorrt模型进行目标检测
部署YOLOv9的TensorRT模型进行目标检测是一个涉及多个步骤的过程,主要包括准备环境、模型转换、编写代码和模型推理。 首先,确保你的开发环境已安装了NVIDIA的TensorRT。TensorRT是一个用于高效推理的SDK,它能对TensorFlow、PyTorch等框架训…...
eureka注册中心做了哪些事情/原理?
1.服务注册: 将eureka client发送过来的元数据存储到注册表中 2.服务续约: eureka client默认会每30秒向eureka server发送一次心跳来进行服务续约,通过这一行动来表示自己没有出现故障; 3.服务…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
