山东省建设工程执业资格中心网站/百度搜索网站优化

山东省建设工程执业资格中心网站,百度搜索网站优化,济南高新网站制作,怎样做黄色网站前面几篇遇到updateQueue的时候,我们把它先简单的当成了一个队列处理,这篇我们来详细讨论一下这个更新队列。 有关updateQueue中的部分,可以见源码 UpdateQueue实现 Update对象 我们先来看一下UpdateQueue中的内容,Update对象&…

前面几篇遇到updateQueue的时候,我们把它先简单的当成了一个队列处理,这篇我们来详细讨论一下这个更新队列。 有关updateQueue中的部分,可以见源码  UpdateQueue实现

Update对象

我们先来看一下UpdateQueue中的内容,Update对象,其实现如下:

/** 更新的Action 可以是State 也可以是函数 */
export type Action<State> = State | ((prevState: State) => State);
/** 定义Dispatch函数 */
export type Dispatch<State> = (action: Action<State>) => void;/** 更新对象 */
export class Update<State> {next: Update<State>;action: Action<State>;lane: Lane; // 当前更新的优先级Laneconstructor(action: Action<State>, lane: Lane) {this.action = action;this.next = null;this.lane = lane;}
}

其中,包含

  • action: Action对象,可以是任意类型,对应的我们在setState中传入的参数,如果传入一个函数,对应的是函数类型action,则运行函数得到状态值。如果不是函数,则直接将其作为状态值。
  • lane: 当前更新对应的优先级lane
  • next: 涉及到updateQueue的数据结构,指向下一个Update对象 

我们在很多地方都需要创建更新对象,比如dispatchSetState是,即你修改状态的时候

 初始化的时候,在updateContainer中,也会创建update对象

updateQueue - 环形链表 

updateQueue本质上是一个存储Update对象的数据结构,但是其不是一个普通的数组,其内部实现了一个环形链表用来存储Update对象,其定义如下

export class UpdateQueue<State> {shared: {pending: Update<State> | null;};/** 派发函数 */dispatch: Dispatch<State>;/** 基础队列 */baseQueue: Update<State> | null;/** 基础state */baseState: State;
...
}

其内部包含shared属性,指向一个对象,对象中包含pending对象,指向Update对象,如下图所示

其中,Update对象的next指针指向下一个Update对象,其组成一个环形链表,如图所示:

其中:

  • updateQueue.shared.pending指向最后一个Update节点
  • updateQueue.shared.pending.next 为第一个Update节点 

 为什么使用环形链表?

这里使用环形链表的一个好处是,其可以很方便的找到首位元素,可以方便的遍历链表,也可以方便的对两个链表进行拼接,这个在后面的baseQueue 和 baseState逻辑中会用到。

 enqueue入队

enqueue为UpdateQueue的类方法,其作用就是给队列插入Update对象,其实现如下:

 /** 入队,构造环状链表 */enqueue(update: Update<State>, fiber: FiberNode, lane: Lane) {if (this.shared.pending === null) {// 插入第一个元素,此时的结构为// shared.pending -> firstUpdate.next -> firstUpdateupdate.next = update;this.shared.pending = update;} else {// 插入第二个元素update.next = this.shared.pending.next;this.shared.pending.next = update;this.shared.pending = update;}/** 在当前的fiber上设置lane */fiber.lanes = mergeLane(fiber.lanes, lane);/** 在current上也设置lane 因为在beginwork阶段 wip.lane = NoLane 如果bailout 需要从current恢复 */const current = fiber.alternate;if (current) {current.lanes = mergeLane(current.lanes, lane);}}

我们用一个插入队列来演示插入过程:

// 假设有插入队列
enqueue(100)
enqueue(current => current + 1)
enqueue(200)

插入100, 100对应的pending.next指向自己,此时100对应的Update又是首节点也是尾节点

插入curr=>curr+1的update节点,此时首节点为pending.nexy也就是 curr=>curr+1 尾节点为100

插入200节点,此时首节点为200 尾节点为100 都是从pending.next的位置插入,如图

设置lane

enqueue方法除了传入更新对象,还需要传入更新所发生在的Fiber对象和对应的更新lane,其目的是在当前更新的Fiber上记录lane,其逻辑如下:

    /** 在当前的fiber上设置lane */fiber.lanes = mergeLane(fiber.lanes, lane);/** 在current上也设置lane 因为在beginwork阶段 wip.lane = NoLane 如果bailout 需要从current恢复 */const current = fiber.alternate;if (current) {current.lanes = mergeLane(current.lanes, lane);}

可以看到,当前更新的fiber节点的alternate节点的lanes也被设置了,这是为了先保存当前的lanes方便后面中短渲染 如bailout的时候能恢复当前fiber的lanes

processQueue - 处理更新

process函数的作用就是处理当前队列的所有更新,在不考虑优先级的情况下,其实现可以简化为如下代码:

  /** 处理任务 */process() {// 当前遍历到的updatelet memorizedState;let currentUpdate = this.baseQueue?.next;if (currentUpdate) {do {const currentAction = currentUpdate.action;if (currentAction instanceof Function) {/** Action是函数类型 运行返回newState */memorizedState = currentAction(memorizedState);} else {/** 非函数类型,直接赋给新的state */memorizedState = currentAction;}currentUpdate = currentUpdate.next;} while (currentUpdate !== this.baseQueue?.next);}return  memorizedState;}

即循环遍历整个环状链表,对action的类型进行检测,如果是函数则运行,如果是非函数直接把ation赋给memorizedState,最后将memorizedState返回即可! 

引入优先级lane

如果加入优先级lane的处理逻辑,process的处理逻辑会稍微有些复杂,我们看个例子

onClick={()=>{// 同步更新Lane = 1setvariable(100)startTransition(()=>{// 可以理解为 创建一个优先级lane=8的UpdatesetVariable(curr=>curr+100)    })// 同步更新Lane = 1setVariable(curr => curr + 100)}}

在一个onClick函数中,我们设置了三次setVariable函数,其中,第二次setter使用startTranstion包裹,这个函数由useTranstion hook提供,这个后面再讲,你可以先理解为,在这个startTransition包裹的setter对应的优先级都会被改成 8 即可 TransitionLane

此时,variable hook中的updateQueue对应的shared.pending队列如下:

由于队列中的优先级不同,我们一次只处理一个优先级的Update对象,对于其他优先级的对象需要进行跳过。

但是需要注意,被我们跳过的更新需要在后面的更新中被执行,并且,虽然我们通过优先级把一次更新拆分成了两次更新,但是最终的结果需要是一样的。

比如,第一次更新 

执行 action 100

跳过 curr=>curr+100 并且记住此时的状态100

执行curr => curr + 200

此时的结果为 300

第二次更新,需要从上次执行到的位置重新执行

执行curr=>curr+100 结果为200

执行 curr=>curr+200 (虽然此Update执行过了,但是为了保证结果一致,还需执行)结果为400

注意,虽然拆成了两次更新,但是最终更新的结果一定是和不加startTranstion按顺序执行的结果一样的!

这样我们就可以把高耗时的更新操作设置低优先级,先处理低耗时的更新,同时保证最终结果不变。

实现这样逻辑的算法如下:

准备一个memorizedState,记录当前updateQueue的状态值

准备一个baseState 用来记录第一个 跳过第一个Update时的状态值

准备一个baseQueue,用来记录本次更新跳过的更新对象 和 跳过更新之后的更新对象, 下一次更新就用这个baseQueue中的Update

遍历队列元素,使用isSubsetOfLanes来判断当前Update.lane是不是等于当前正在更新的lane(wipRenderedLane)

如果是则看baseQueue队列

   如果baseQueue队列为空, 则执行action,给memorizedState赋值

   如果baseQueue队列不为空, 则说明当前更新前面,已经有跳过的Update被加入到baseQueue了,那么其后面所有的Update对象都要加入baseQueue,则把当前Update对象克隆一份,并且设置优先级为Nolanes,以保证下次更细当前Update一定能被执行,推入baseQueue

并且,由于当前Update的lane是满足的,需要执行action,更新memorizedState

如果不是, 看updateQueue队列

 如果队列为空,此时为第一个跳过的Update对象,把当前的Update对象克隆一 份push到baseQueue中,并且把当前memorizedState赋给baseState,记录本次更新第一个跳过Update对应的状态,下次更新就从此开始

如果队列不为空,和上面一样,区别就是不赋baseState了,注意baseState只有第一次更新才设置

最后返回 memorizedState 并且把baseState baseQueue记录在当前updateQueue对象上,复习一下UpdateQueue的ts定义。

export class UpdateQueue<State> {shared: {pending: Update<State> | null;};/** 派发函数 */dispatch: Dispatch<State>;/** 基础队列 */baseQueue: Update<State> | null;/** 基础state */baseState: State;
...
}

下面我们画图来解释一下 Update队列如下:

Update List 
[action: 100,lane: 1]
[action: curr => curr + 100, lane: 8]
[action: curr = curr+ 200,lane: 1]

此时的updateQueue和状态如下: 

此时的root.pendinglanes 包含lane1 和 lane8 即SyncLane和TranstionLane

开始更新最高的优先级lane1 , 处理第一个Update,由于满足优先级,直接计算并且更新memorizedState = 100

继续处理到curr=>curr+100 此时lane=8 需要跳过,但是此时baseQueue为空,为第一个跳过的更新,需要baseState记录跳过之前的memorizedState = 100,并且克隆一份Update 推入baseQueue

 

继续处理curr=>curr+200 此时满足lane=1 但是由于baseQueue已经不为空,则后面所有的Update无论什么优先级,都需要克隆一份Update对象并且设置lanes为NoLane 推入baseQueue

同时需要计算action更新memorizedState为300

 

第一轮更新结束,此时状态为300,保存baseState和baseQueue并且删除shared.pending队列,因为已经用不上了。

第二轮更新 lane=8 此时从baseQueue中取出上次跳过的更新,继续处理,此时memorizedState被baseState初始化为100

 处理第一个更新,此时memorizedState=200

处理第二个更新,由于是任意Lanes&NoLanes === NoLanes 所以第二个update也满足优先级,更新memorizedState=400 此时完成更新 

 最终结果为400

两次更新,第一次更新值为300 第二次更新值为400 做到了过渡的作用

如果页面中包含逻辑,如果variable === 400 则渲染10000个li 此时如果不用startTranstion降低优先级,则更新variable到400的那次更新的优先级lane=1 那么此时如果有更高优先级任务来,则此次lane=1的更新无法被打断,导致页面卡住不动 影响用户体验。

如果更新到400的更新优先级为8 那么当更高优先级更新来的时候,此次大规模的更新会被打断,优先执行更高优先级更新(比如用户事件) 在高优先级任务执行完成之后,再执行这个大规模更新渲染,优化了用户体验!

连接baseQueue和pending

每一轮更新之后,pending对应的update环会被清空,但是当处理本次更新的时候,又有新的update被挂上,此时baseQueue和pending都有值

比如,在某次更新的useEffect中,设置了setVariable 此时的更新队列中又有新的更新了

此时就需要把baseQueue队列和pending队列连接,baseQueue队列在前

需要定义两个变量 baseFirst 和 pendingFirst 分别指向baseQueue和pending的对头,因为改变过pending/baseQueue.next 之后 就无法直接找到队头元素

第一步 设置baseQueue.next = pendingFirst 把baseQueue尾和pending头连接 如图

 第二步 Pending.next = baseFirst 此时pending队列的尾和baseQueue头连接 如图

此时 baseFirst 就是整个队列的头部了

说完了原理,我们看一下process方法的完整实现:

  /** 处理任务 */process(renderLane: Lane, onSkipUpdate?: (update: Update<any>) => void) {/** 获取baseQueue pending 完成拼接 */let baseState = this.baseState;let baseQueue = this.baseQueue;const currentPending = this.shared.pending;// 生成新的baseQueue过程if (currentPending !== null) {if (baseQueue !== null) {// 拼接两个队列// pending -> p1 -> p2 -> p3const pendingFirst = currentPending.next; // p1// baseQueue -> b1->b2->b3const baseFirst = baseQueue.next; // b1// 拼接currentPending.next = baseFirst; // p1 -> p2 -> p3 -> pending -> b1 -> b2 -> b3baseQueue.next = pendingFirst; //b1-> b2 -> b3 -> baseQueue -> p1 -> p2 -> p3// p1 -> p2 -> p3 -> pending -> b1 -> b2 -> b3 baseQueue}// 合并 此时 baseQueue -> b1 -> b2 -> b3 -> p1 -> p2 -> p3baseQueue = currentPending;// 覆盖新的baseQueuethis.baseQueue = baseQueue;// pending可以置空了this.shared.pending = null;}// 消费baseQueue过程// 设置新的basestate和basequeuelet newBaseState: State = baseState;let newBaseQueueFirst: Update<State> | null = null;let newBaseQueueLast: Update<State> | null = null;// 新的计算值let memorizedState: State = baseState;// 当前遍历到的updatelet currentUpdate = this.baseQueue?.next;if (currentUpdate) {do {const currentUpdateLane = currentUpdate.lane;// 看是否有权限if (isSubsetOfLanes(renderLane, currentUpdateLane)) {// 有权限if (newBaseQueueFirst !== null) {// 已经存在newBaseFirst 则往后加此次的update 并且将此次update的lane设置为NoLane 保证下次一定能运行const clone = new Update(currentUpdate.action, NoLane);newBaseQueueLast = newBaseQueueLast.next = clone;}if (currentUpdate.hasEagerState) {memorizedState = currentUpdate.eagerState;} else {// 不论存不存在newBaseFirst 都要计算memorizedStateconst currentAction = currentUpdate.action;if (currentAction instanceof Function) {/** Action是函数类型 运行返回newState */memorizedState = currentAction(memorizedState);} else {/** 非函数类型,直接赋给新的state */memorizedState = currentAction;}}} else {// 无权限const clone = new Update(currentUpdate.action, currentUpdate.lane);if (onSkipUpdate) {onSkipUpdate(clone);}// 如果newBaseQueueFirst === null 则从第一个开始添加newbaseQueue队列if (newBaseQueueFirst === null) {newBaseQueueFirst = newBaseQueueLast = clone;// newBaseState到此 不在往后更新 下次从此开始newBaseState = memorizedState;} else {newBaseQueueLast = newBaseQueueLast.next = clone;}}currentUpdate = currentUpdate.next;} while (currentUpdate !== this.baseQueue?.next);}if (newBaseQueueFirst === null) {// 此次没有update被跳过,更新newBaseStatenewBaseState = memorizedState;} else {// newbaseState不变 newBaseQueueFirst newBaseQueueLast 成环newBaseQueueLast.next = newBaseQueueFirst;}// 保存baseState和BaseQueuethis.baseQueue = newBaseQueueLast;this.baseState = newBaseState;return { memorizedState };}

 

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

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

相关文章

[SQL] 事务的四大特性(ACID)

&#x1f384;事务的四大特性 以下就是事务的四大特性&#xff0c;简称ACID。 原子性&#x1f4e2;事务时不可分割的最小操作单元&#xff0c;要么全部成功&#xff0c;要么全部失败。一致性&#x1f4e2;事务完成后&#xff0c;必须使所有的数据都保持一致隔离性&#x1f4e2…

DeepSeek 提示词:基础结构

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

Apache DolphinScheduler系列1-单节点部署及测试报告

文章目录 整体说明一、部署环境二、版本号三、部署方案四、部署步骤4.1、上传部署包4.2、创建外部数据库4.3、修改元数据库配置4.4、上传MySQLl驱动程序4.5、初始化外部数据库4.6、启停服务4.7、访问页面五、常见问题及解决方式5.1、时间不一致5.2、异常终止5.3、大量日志5.4、…

LLM之论文阅读——Context Size对RAG的影响

前言 RAG 系统已经在多个行业中得到广泛应用&#xff0c;尤其是在企业内部文档查询等场景中。尽管 RAG 系统的应用日益广泛&#xff0c;关于其最佳配置的研究却相对缺乏&#xff0c;特别是在上下文大小、基础 LLM 选择以及检索方法等方面。 论文原文: On the Influence of Co…

LLaMA-Factory|微调大语言模型初探索(4),64G显存微调13b模型

上篇文章记录了使用lora微调deepseek-7b&#xff0c;微调成功&#xff0c;但是微调llama3-8b显存爆炸&#xff0c;这次尝试使用qlora微调HQQ方式量化&#xff0c;微调更大参数体量的大语言模型&#xff0c;记录下来微调过程&#xff0c;仅供参考。 对过程不感兴趣的兄弟们可以直…

详解Redis如何持久化

引言 本文介绍了 Redis 的两种持久化方式&#xff1a;RDB 和 AOF。RDB 按时间间隔快照存储&#xff0c;AOF 记录写操作。阐述了它们的配置、工作原理、恢复数据的方法、性能与实践建议&#xff0c;如降低 fork 频率、控制内存等&#xff0c;还提到二者可配合使用&#xff0c;最…

HarmonyOS Design 介绍

HarmonyOS Design 介绍 文章目录 HarmonyOS Design 介绍一、HarmonyOS Design 是什么&#xff1f;1. 设计系统&#xff08;Design System&#xff09;2. UI 框架的支持3. 设计工具和资源4. 开发指南5. 与其他设计系统的对比总结 二、HarmonyOS Design 特点 | 应用场景1. Harmon…

Java 大视界 -- 基于 Java 的大数据机器学习模型压缩与部署优化(99)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

算法-图-数据结构(邻接矩阵)-BFS广度优先遍历

邻接矩阵广度优先遍历&#xff08;BFS&#xff09;是一种用于遍历或搜索图的算法&#xff0c;以下是具体介绍&#xff1a; 1. 基本概念 图是一种非线性的数据结构&#xff0c;由顶点和边组成&#xff0c;可分为无向图、有向图、加权图、无权图等。邻接矩阵是表示图的一种数…

ARM Cortex-M3 技术解析:核寄存器R1-R15介绍及使用

ARM Cortex-M3 技术解析&#xff1a;核寄存器R1-R15介绍及使用 作为嵌入式开发领域的经典处理器内核&#xff0c;ARM Cortex-M3&#xff08;CM3&#xff09;凭借其高效能、低功耗和丰富特性&#xff0c;在工业控制、物联网、消费电子等领域广泛应用。而内核寄存器是我们调试代…

DeepSeek+Kimi生成高质量PPT

DeepSeek与Kimi生成PPT全流程解析 一、工具分工原理 DeepSeek核心作用&#xff1a;生成结构化PPT大纲&#xff08;擅长逻辑构建与内容优化&#xff09;Kimi核心作用&#xff1a;将文本转换为视觉化PPT&#xff08;提供模板库与排版引擎&#xff09; 二、操作步骤详解 1. 通…

Redis|持久化

文章目录 总体介绍RDB&#xff08;Redis DataBase&#xff09;官网介绍案例演示优势劣势如何检查修复 dump.rdb 文件哪些情况下会触发 RDB 快照如何禁用快照RDB 优化配置项详解小总结 AOF&#xff08;Append Only File&#xff09;官网介绍是什么能干嘛AOF 持久化工作流程AOF 缓…

巨控科技的GRM550元出魔抗实现PLC远程下载与维护方案:工业自动化的高效解决方案

巨控科技PLC远程下载与维护方案&#xff1a;工业自动化的高效解决方案 在工业自动化领域&#xff0c;设备的高效维护与快速调试是保障生产连续性的关键。巨控科技推出的PLC远程下载与维护方案&#xff0c;凭借其先进的技术和广泛兼容性&#xff0c;成为企业实现设备远程管理的…

ChatGLM2-6B如何从输入到输出-代码解析(二)

出发点 上一篇解析了Chatglm2-6b的模型架构&#xff0c;并和Chatglm-6b进行对比&#xff0c;但是留下了几个问题&#xff08;哭&#xff09;这一篇的目的是讲明白attention和rotaryEmbedding&#xff0c;解决问题&#xff0c;并实现整体目标&#xff0c;完全替代modeling_chat…

Sublime Text4安装、汉化

-------------2025-02-22可用---------------------- 官方网址下载&#xff1a;https://www.sublimetext.com 打开https://hexed.it 点击打开文件找到软件安装目录下的 ctrlf 查找 8079 0500 0f94 c2右边启用替换替换为:c641 0501 b200 90点击替换按钮 替换完成后 另存为本地…

汽车开放系统架构(AUTOSAR)中运行时环境(RTE)生成过程剖析

一、引言 在当今高度智能化的汽车电子领域&#xff0c;软件系统的复杂性呈指数级增长。为了应对这一挑战&#xff0c;汽车开放系统架构&#xff08;AUTOSAR&#xff09;应运而生&#xff0c;它为汽车电子软件开发提供了标准化的分层架构和开发方法。其中&#xff0c;运行时环境…

stm32仿真 74hc238流水灯 数码管动态数字显示

f103c6t6a_hex文件 #include "main.h"![请添加图片描述](https://i-blog.csdnimg.cn/direct/8c0d44b121134cf08f5186df316ea07f.gif)#include "stdlib.h"void SystemClock_Config(void); static void MX_GPIO_Init(void); // 自定义abc引脚 #define A_PIN…

网络层(IP)

基本概念 子网和局域网是一个概念主机: 配有 IP 地址, 也能进行路由控制的设备;路由器: 即配有 IP 地址, 又能进行路由控制;节点&#xff1a; 路由器和主机的统称。 背景 两主机并不是直接连接的&#xff0c;路径选择问题&#xff1f;为什么&#xff1f; 由网络层&#xff08…

【初阶数据结构】链表的柔光之美

目录 一、为什么需要链表&#xff1f; 二、链表与数组的对比 三、链表节点定义 四、链表基本操作 1. 创建链表 2. 插入节点 头插法&#xff08;时间复杂度O(1)&#xff09; 尾插法&#xff08;时间复杂度O(n)&#xff09; 3. 删除节点 4. 遍历链表 五、进阶操作 1. 反…

MybatisPlus-扩展功能-枚举处理器

在Mybatis里有一个叫TypeHandler的类型处理器&#xff0c;我们常见的PO当中的这些成员变量的数据类型&#xff0c;它都有对应的处理器&#xff0c;因此它就能自动实现这些Java数据类型与数据库类型的相互转换。 它里面还有一个叫EnumOrdinalTypeHandler的枚举处理器&#xff0…