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触发(宏任务),故体现为异步

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.currentHostRootFiber

  • 作用是给 HostRootFiber 创建一个 alternate副本

  • workInProgress 指针指向这个副本, 即 workInProgress = HostRootFiber.alternate

  • 在前文 double buffering 中分析过,HostRootFiber.alternate 是正在构造的fiber树的根节点

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

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

相关文章

18个惊艳的可视化大屏(第六辑):地图焦点

本期带来的都是以地图作为视觉焦点的可视化大屏页面。

动态规划课堂1-----斐波那契数列模型

目录 动态规划的概念&#xff1a; 动态规划的解法流程&#xff1a; 题目: 第 N 个泰波那契数 解法&#xff08;动态规划&#xff09; 代码&#xff1a; 优化&#xff1a; 题目&#xff1a;最小花费爬楼梯 解法&#xff08;动态规划&#xff09; 解法1&#xff1a; 解…

独立站建站全攻略:从0到1打造专属在线商业平台

独立站建站全攻略&#xff1a;从0到1打造专属在线商业平台 随着互联网的普及和发展&#xff0c;越来越多的企业和个人开始认识到拥有一个独立站的重要性。独立站不仅可以提升品牌形象&#xff0c;还能为企业带来更多的流量和潜在客户。本文将为大家详细介绍独立站建站的全过程…

如何实现不同 Vue 项目的 npm 和 Node.js 环境进行隔离

方法一&#xff1a;使用 nvm&#xff08;Node Version Manager&#xff09; nvm 是一个用于管理多个 Node.js 版本的工具。通过 nvm&#xff0c;你可以为每个 Vue 项目安装和使用不同版本的 Node.js 和 npm。首先&#xff0c;安装 nvm&#xff1a;对于 macOS 和 Linux&#xf…

【深度学习笔记】卷积神经网络——汇聚层(池化层)

汇聚层&#xff08;池化层&#xff09; 通常当我们处理图像时&#xff0c;我们希望逐渐降低隐藏表示的空间分辨率、聚集信息&#xff0c;这样随着我们在神经网络中层叠的上升&#xff0c;每个神经元对其敏感的感受野&#xff08;输入&#xff09;就越大。 而我们的机器学习任…

VsCode的leetcode插件无法登录

前提 想使用VsCode的leetcode插件进行刷题&#xff0c;然后按照网上的教程进行安装下载&#xff0c;但是到了登录这一步&#xff0c;死活也登录不了&#xff0c;然后查看log一直报的错误是invalid password。 解决方法 首先确定在插件中设置的站点是Leetcode中国&#xff0c…

图像处理新框架 | 语义与复原指令双引擎,谷歌研究院提出文本驱动图像处理框架TIP

本文首发: AIWalker 欢迎关注AIWalker&#xff0c;底层视觉与基础AI技术 https://arxiv.org/abs/2312.14091 https://github.com/Picsart-AI-Research/HD-Painter 基于文本到图像扩散模型的空前成功&#xff0c;文本引导图像修复的最新进展已经可以生成非常逼真和视觉上合理的结…

C++面试:linux系统性能监控命令的使用

目录 1. top 2. vmstat 3. iostat 4. mpstat 5. netstat 6. sar 7. htop 8. dstat 9. free 10. lsof 11. pidstat 12. nmon 13. iftop 14. glances 面试准备小贴士 在Linux系统管理和故障排查中&#xff0c;使用性能监控工具是非常重要的。这些工具可以帮助你理…

centos7部署单机项目和自启动

centos7部署单机项目和服务器自启动 1.安装jdk和tomact1.1上传jdk、tomcat安装包1.2解压两个工具包1.3.配置并且测试jdk安装1.4.启动tomcat1.5.防火墙设置1.6配置tomcat自启动 2.安装mysql2.1卸载mariadb&#xff0c;否则安装MySql会出现冲突(先查看后删除再查看)2.2在线下载My…

【爬虫逆向实战篇】定位加密参数、断点调试与JS代码分析

文章目录 1. 写在前面2. 确认加密参数3. 加密参数定位4. XHR断点调试 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向…

python-分享篇-用python制作九宫格切图器

文章目录 代码效果 代码 import tkinter as tk from PIL import Image,ImageTk import sys import tkinter.filedialog#先将图片填充为正方形 def fill_image(image): width, height image.size #比较图片的宽和高&#xff0c;选取值较大的作为新图的宽 newImage_width wid…

仿12306校招项目-项目业务和架构

目录 业务图 用户管理 业务难点 1. 如何确定用户注册信息的真实性 2. 面对亿级用户量 3. 支持多种登录方式会造成读请求扩散&#xff0c;需要解决用户定位问题 4. 高并发场景下缓存穿透问题需要有效解决&#xff0c;避免数据库压力过大 5. 明文存储用户敏感信息会造成安…

抽象的java

Consider defining a bean of type org.springframework.mail.MailSender in your configuration. 报错原因&#xff1a; 第一个&#xff1a;未安装对应的依赖 第二个&#xff1a;对应配置问题 背景&#xff1a;用springboot-java完成邮箱发送 第一个问题解决方法&#xff1…

实战一个 Jenkins 构建 CI/CD流水线 的简单配置过程哈

引言&#xff1a;上一期我们讲述了gitlabCI/CD工具的介绍&#xff0c;工具之争&#xff0c;本期我们介绍Jenkins CI/CD 目录 一、Jenkins介绍 1、Jenkins概念 2、Jenkins目的 3、特性 4、产品发布流程 二、安装Jenkins 1、安装JDK 2、安装Jenkins 1、上传压缩包 2、…

Spark之【数据倾斜】

Spark程序运行变慢&#xff0c;十有八九出现了数据倾斜。那么什么是数据倾斜、导致数据倾斜的原因以及如何克服它以保持Spark应用程序的最佳性能呢&#xff1f; 什么是数据倾斜&#xff1f; Apache Spark中的数据倾斜指的是&#xff0c;在处理的数据其在不同分区之间分布不均…

基于django的购物商城系统

摘要 本文介绍了基于Django框架开发的购物商城系统。随着电子商务的兴起&#xff0c;购物商城系统成为了许多企业和个人创业者的首选。Django作为一个高效、稳定且易于扩展的Python web框架&#xff0c;为开发者提供了便捷的开发环境和丰富的功能模块&#xff0c;使得开发购物商…

spring security 防止已经完成认证的会话再次访问login页面

版本 spring-security:6.2.1 方案 添加过滤器检查会话认证信息&#xff0c;如果包含非匿名用户的认证信息则调用认证成功处理器 final static String URL_LOGIN "/login"; final AuthenticationSuccessHandler authenticationSuccessHandler new SavedRequestA…

GEE入门篇|遥感专业术语(实践操作3):时间分辨率(Temporal Resolution)

目录 时间分辨率&#xff08;Temporal Resolution&#xff09; 1.Landsat 2.Sentinel-2 时间分辨率&#xff08;Temporal Resolution&#xff09; 时间分辨率是指特定传感器图像流的重访时间或时间节奏&#xff0c;重访时间是指卫星连续访问地球表面同一位置…

小迪安全30WEB 攻防-通用漏洞SQL 注入CTF二次堆叠DNS 带外

#知识点&#xff1a; 1、数据库堆叠注入 根据数据库类型决定是否支持多条语句执行 2、数据库二次注入 应用功能逻辑涉及上导致的先写入后组合的注入 3、数据库 Dnslog 注入 解决不回显(反向连接),SQL 注入,命令执行,SSRF 等 4、黑盒模式分析以上 二次注入&…

创作纪念日:记录我的成长与收获

机缘 一开始是在我深入学习前端知识的Vue.js框架遇到了一个问题&#xff0c;怎么都解决不了&#xff0c;心烦意乱地来csdn上找解决方法。开心的是真被我找到了&#xff0c;真的很感恩&#xff0c;也意识到在这个平台上分享自己的经验是多么有意义的事情&#xff0c;可能随便的…