一文搞懂React18原理


前言

可能一直在写代码的路上,大多数时候就是个工具人。今天一起看看react18的实现原理,持续学习中。


React 初始化过程

react 在渲染过程中要做很多事,不会直接通过初始元素直接渲染。还有虚拟节点。除了初始元素能生成虚拟节点外,还有哪些能生成虚拟节点?

节点类型

  1. DOM 节点:虚拟 DOM 节点。当初始元素的 type 为字符串的时候 react 就会创建虚拟 DOM 节点
  2. 组件节点:当初始元素的 type 为函数或是类的时候,react 就会创建虚拟组件节点
  3. 文本节点:直接书写字符串或者数字,react 会创建文本节点
  4. 空节点:react 代码中的三目表达式 a>b?1:false,用来条件渲染,当为 false 时不会渲染。其实遇到字面量 null,false,true,undefined 在 react 中均会被创建为一个空节点,在渲染时遇到空节点将什么都不会渲染
  5. 数组节点:不是直接渲染数组本身,当 react 遇到数组时会创建数组节点,但是不会直接进行渲染,而是将数组的每一项拿出来,根据不同的节点类型去做相应的事情。所以数组里的每一项只能是上面这五种节点类型

react 工作是通过初始元素或者可以生成虚拟节点的东西生成虚拟节点,然后针对不同的节点类型去做不同的事情最终生成 DOM 挂载到页面上。

首次渲染阶段

1.初始元素—DOM 节点

针对初始元素的 type 属性为字符串时,react 会通过 document.createElement 创建真实 DOM。因为初始元素的 type 为字符串,所以会根据 type 属性创建不同的真实 DOM。创建完真实 DOM 后会立即设置该真实 DOM 的属性,比如直接在 jsx 中可以直接书写 className,style 等等都会作用到真实的 DOM 上。

2.初始元素—组件节点

如果初始元素的 type 属性是一个 class 类或者 function 函数时,那么会创建一个组件节点。所以针对类或函数组件,处理是不同的。

函数组件

对于函数组件会直接调用函数,将函数的返回值进行递归处理(看看是什么节点类型,然后去做对应的事情,所以一定要返回能生成虚拟节点的东西),最终生成一颗 vDom 树。

类组件

对于类组件而言相对会麻烦

  • 首先创建类的实例(调用 constructor)

  • 调用生命周期方法 static.getDerivedStateFromProps

  • 调用生命周期方法 render,根据返回值递归处理,跟函数组件处理返回值一样,最终生成一颗 vDom 树

  • 将组件的生命周期方法 componentDidMount 加入到队列中等待真实 DOM 挂载到页面后执行(前面说 render 是一个递归处理,所以如果一个组件存在父子关系的时候,那么肯定要等到子组件渲染完父组件才能走出 render,所以子组件的 compontDidMount 一定是比父组件先入队列的,肯定先运行)

    3.文本节点

针对文本节点,会直接通过 document.createTexNode 创建真实的文本节点

4.空节点

如果生成的是空节点,那么将什么都不会做

5.数组节点

react 不会直接渲染数组,而是将数组的每一项拿出来遍历,根据不同的节点类型去做相应的事情,直到递归处理完数组里的每一项

总结:处理完所有的节点后,我们的 vDom 树和真实 DOM 也会创建好,react 会将 vDom 树保存起来,方便后续使用。然后将真实 DOM 都挂载到页面上。

React 更新过程

更新场景

1.组件更新(setState)

经常用 setState 来重新设置组件的状态进行重新渲染。使用 setState 只会更新调用此方法的类。不会设计到兄弟节点以及父级节点。影响范围仅仅是自己的子节点,步骤如下:

  1. 运行当前类组件的生命周期方法 static.getDerivedStateFromProps。根据返回值合并当前组件的状态
  2. 运行当前类组件的生命周期方法 shouldComponentUpdate。如果该方法返回 false,直接终止更新流程
  3. 运行当前类组件的生命周期方法 render,得到一个新的 vDom 树,进入新旧两棵树的对比更新
  4. 将当前类组件的生命周期方法 getSnapshotBeforeUpdate 加入执行队列,等待将来执行
  5. 将当前类组件的生命周期方法 componentDidUpdate 加入执行队列,等待将来执行
  6. 重新生成 vDom 树
  7. 执行队列,此队列存放的是更新过程设计到原本存在的类组件生命周期方法 getSnapshotBeforeUpdate
  8. 根据 vDom 树更新真实 DOM
  9. 执行队列,此队列存放的是更新过程中所涉及到原本存在的生命周期方法 componentDidUpdate
  10. 执行队列,此队列存放的是更新过程中所有卸载类组件的生命周期方法 componentWillUnMount

2.根节点更新(ReactDOM.createRoot().render)

在 ReactDOM 的新版中,不在直接使用 ReactDOM.render 进行更新,而是通过 createRoot(要控制的 DOM 区域)的返回值来调用 render

对比更新过程(diff)

对比更新就是将新 vDom 树和之前首次渲染过程中保存的老 vDom 树对比发现差异后做一系列操作的过程。

React 的 diff 算法将之前的复杂度 O(n^3)降为了 O(n),它做了以下几个假设

  1. 假设此次更新的节点层级不会发生移动(直接找到就树中的位置对比)
  2. 兄弟节点之间通过 key 进行唯一标识
  3. 如果新旧节点类型不相同,那么它认为就是一个新的结构,比如之前就是初始元素 div 现在变成初始元素 span 那么它会认为整个结构全部变了,无论嵌套了多深也会全部丢弃重新创建

key 作用

为了通过旧节点,寻找对应的新节点进行对比提高节点的复用率。

React Fiber 架构

单线程 CPU 调度策略

1.先到先得(First-Come-First-Server,FCFS)

最简单调度策略,简单说就是没有调度。谁先来谁就先执行,如果中间某些进程因为 I/O 阻塞,这些进程会挂起移回就绪队列(重新排队)

2.轮转调度

基于时钟的抢占策略,也是抢占策略中最简单的一种:公平的给每一个进程一定的执行时间,当时间消耗完毕或阻塞,操作系统就会调度其他进程,将执行权抢占过来

要点是确定合适的时间片长度:太长了,长进程霸占太久资源,其他进程得不到响应(等待时间过长),太短了,进程抢占和切换都是需要成本的,而且成本不低,时间片太短,时间浪费给在上下文切换上,导致进程干不了什么实事。

因此,时间片的长度最好符合大部分进程完成一次典型交互所需的时间

3.最短进程优先(Shortest Process Next,SPN)

按照进程的预估执行时间对进程进行优先级排序,先执行完短进程,后执行长进程,这是一种非抢占策略

SPN 缺点:如果系统有大量短进程,那么长进程可能会饥渴得不到响应

4.最短剩余时间(Shortest Remaining Time,SRT)

5.最高响应比优先(HRRN)

6.反馈法

分片设计

前端如何解决

  1. 优化每个任务,让它有多快就多快,挤压 cpu 运算量
  2. 快速响应用户,让用户觉得够快,不能阻塞用户的交互(React 分片)
  3. 尝试 Worker 多线程

Vue 选择的是 1,使用模板让它有了很多优化的空间,配合响应式机制可以让 Vue 精确的进行节点更新。

React 选择的是 2。

对于 Worker 多线程渲染方案有人尝试,但是要保证状态和试图的一致性相当麻烦

React 是如何优化的

为了给用户一种应用很快的假象,不能让一个程序长期霸占资源,可以将浏览器的渲染、布局、绘制、资源加载、事件响应、脚本执行视作操作系统的进程,我们需要通过某些调度策略合理的分配 CPU 资源,从而提高浏览器的用户响应速度,同时兼顾任务执行效率。

react 通过 fiber 架构,让自己的 reconcilation 过程变成可被中断的。适时的让出 CPU 执行权,除了可以让浏览器及时的响应用户的交互,还有其他好处。

时间分片

人眼最高识别帧数不超过 30 帧,电源帧数大多固定在 24,浏览器最优的帧率是 60,即 16.5ms 左右渲染一次

但是当 JS 执行时间过长,FPS(每秒显示帧数)下降造成视觉上的卡顿

如何解决,就是 fiber reconciler 要做的事。将要执行的 JS 做分片,保证不会阻塞主线程(Main thread)即可

requestIdleCallback 和 requestAnimation 区别

requestIdleCallback 是在空闲时间执行

requestAnimation 是在下一帧渲染之前执行

React 核心包结构

核心包

1.react

react 基础包,提供 react 组件(ReactElement)的必要函数,一般来说(react-dom,react-native)一同使用,在编写 react 应用的代码时,大部分都是调用此包 api

2.react-dom

react 渲染器之一,是 react 和 web 平台连接的桥梁,将 react-reconeiler 中的运行结果输出到 web 界面,在编写 react 应用时,大多数场景下能用到此包的就是一个入口函数 ReactDOM.render

3.react-reconciler

react 得以运行的核心包(综合协调 react-dom,react,scheduler 各包之间的调用与配合),管理 react 应用状态的输入和输出,将输入信号最终转换成输出信号传递给渲染器

4.react-scheduler

调度机制的核心实现,控制有 react-reconciler 送入回调函数的执行时机,在 concurrent 模式 下可以实现任务分片,在编写 react 应用的代码时,同样几乎不会直接用到此包提供的 api

  • 核心任务就是执行回调(回调函数由 react-reconciler 提供)
  • 通过控制回调函数的执行时机,来达到任务分片的目的,实现可中断渲染(concurrent 模式下才有此特性)

React 工作循环

react 核心就是下面两大工作循环

1.react-reconciler

核心:构建 fiber 树,生成任务

2.scheduler

核心:任务调度,任务优先级

React 核心对象

ReactElement 对象

在 jsx 语法中书写的节点,都会变编译器转换,最终会以 React.createElement(…)的方式创建出来一个 ReactElement 对象

主要 2 个属性:

1.key:在 reconciler 阶段会用到,默认值是 null,在 diff 算法中会使用到

2.type:这个属性决定了节点的种类:

在 reconciler 阶段会根据 type 执行不同的逻辑

它的值可以是字符串(div,span 等 dom 节点),函数(function,class 等节点),或者 react 内部定义的节点类型(portal,context,fragment 等)

  • 如 type 是字符串类型,则直接使用
  • 如 type 是 ReactComponent 类型,则会调用 render 方法获取子节点
  • 如 type 是 function 类型,则会调用该方法获取节点

注意:

  • class 和 function 类型的组件,其子节点是在 render 之后(reconciler 阶段)才生成的
  • 父级对象和子级对象通过 props.children 属性进行关联的(与 fiber 树不同)
  • ReactElement 虽然不能算是一个严格的树,也不能算是一个严格的链表,他的生成过程是自顶向下的,是所有的组件节点的总和
  • ReactElement 树和 fiber 树是以 props.children 为单位先后交替生成的,当 ReactElement 树构建完成,fiber 树也随后构建完毕
  • reconciler 阶段会根据 ReactElement 的类型生成对应的 fiber 节点(不是一 一对应的,比如 Fragment 类型的组件在生成 fiber 节点的时候会略过)

fiber 对象

react-reconciler 包是 react 应用的中枢,连接渲染器(react-dom)和调度中心(scheduler),同时自身也负责 fiber 树的构造

fiber.tag:表示 fiber 类型,根据 ReactElement 组件的 type 进行生成,在 react 内部定义了 25 种 tag

fiber.key:和 ReactElement 组件的 key 一致

fiber.return:指向父节点

fiber.child:指向第一个子节点

fiber.sibling:指向下一个兄弟节点

fiber.index:fiber 在兄弟节点中的索引,如果是单节点默认是 0

fiber.ref:指向 ReactElement 组件上设置的 ref(string 类型的 ref 除外,reconciler 阶段会将 string 类型的 ref 转换为一个 function 类型)

fiber.lanes:本 fiber 节点所属的优先级,创建 fiber 的时候设置

fiber.alternate:指向内存中另一个的 fiber,每个被更新过 fiber 节点在内存中都是成对出现(current 和 workInProgress)

UpdateQueue 与 UpdateQueue

属性解释
1.UpdateQueue:

  • baseState:表示此队列的基础 state

  • firstBaseUpdate:指向基础队列的队首

  • lastBaseUpdate:指向基础队列的队尾

  • shared:共享队列

  • effects:用于保存有 callback 回调函数 update 对象,在 commit 之后,会依次调用这里的回调函数

    2.Update:

  • lane:update 所属的优先级

  • tag:表示 update 种类,有 4 种,UpdateState,ReplaceState,ForceUpdate,CaptureUpdate

  • payload:载荷,update 对象真正需要跟新的数据,可以设置成一个回调函数或者对象

  • callback:回调函数,commit 完成之后调用

  • next:指向链表中的下一个,由于 UpdateQueue 是一个环形链表,最后一个 update.next 指向第一个 update 对象

UpdateQueue 是 fiber 对象的一个属性,所以不能脱离 fiber 存在,他们之间数据结构和应用关系如下

Hook 对象

Hook 用于 function 组件中,能够保持 function 组件的状态(与 class 组件中的 state 在性质上是相同的,都是为了保持组件的状态),常用 api 有:useState,useEffect,useCallback 等

属性解释

1.Hook:

  • memoizedState:内存状态,用于输出成最终的 fiber 树、
  • baseState:基础状态,当 Hook.queue 更新过后 baseState 也会更新
  • baseQueue:基础队列状态,在 reconciler 阶段会辅助状态合并
  • queue:指向一个 Update 队列
  • next:指向该 function 组件的下一个 Hook 对象,使得多个 Hook 之间也构成了一个链表

Hook.queue 和 Hook.baseQueue(即 UpdateQueue 和 Update)是为了保证 Hook 对象能够顺利更新,与上下文 fiber.updateQueue 中的 UpdateQueue 和 Update 是不一样的(且他们在不同的文件中)

Hook 与 fiber 的关系:

在 fiber 对象中有一个属性 fiber.memoizedState 指向 fiber 节点的内存状态,在 function 类型的组件中,fiber.memoizedState 就指向 Hook 队列(Hook 队列保持了 function 类型的组件状态)

所以 Hook 也不能脱离 fiber 而存在

Task 对象

scheduler 包中,没有为 task 对象定义 type,其定义是直接在 js 代码中

属性解释:

  • id:位移标识
  • callback:task 最核心的字段,指向 react-reconciler 包所提供的回调函数
  • prioritylevel:优先级
  • startTime:一个时间戳,代表 task 的开始时间(创建时间+演示时间)
  • expirationTime:过期时间
  • sortIndex:控制 task 在队列中的次序,值越小越靠前

注意:task 中没有 next 属性,他不是一个链表,其顺序是通过排序来实现的(小顶锥数组,始终保证数组中的第一个 task 对象的优先级最高)

reconciler

reconciler 执行流程

react-reconciler 包的主要作用,主要功能分为以下 4 个方面:

  1. 输入:暴露 api 函数(如:schedleUpdateOnfiber),供给其它包(如 react 包)调用
  2. 注册调度任务:与调度中心(schedler 包)交互,注册调度任务 task,等待任务回调
  3. 执行任务回调:在内存中构造出 fiber 树,同时与渲染器(react-com)交互,在内存中创建出与 fiber 对应的 DOM 节点
  4. 输出:与渲染器(react-dom)交互,渲染 DOM 节点

在 reconciler 执行过程中

1、Render(基于 task,可以被打断,可以被打断的前提是基于渲染 mode)

  • 初始化 fiber
  • 更新 fiber

2、commit

  • dom 变更之前
  • dom 变更之后
  • dom 更新之后

reconciler 启动过程

创建全局对象

1、ReactDOM(Blocking)Root 对象

  • 属于 react-dom 包,该对象暴露有 render,unmount 方法,通过调用该实例的 render 方法,可以引导 react 应用的启动

2、fiberRoot 对象

  • 属于 react-reconciler 包,作为 react-reconciler 在运行过程中的全局上下文,保存 fiber 构建过程所依赖的全局状态
  • 其它大部分实例变量用来存储 fiber 构建循环过程的各种状态 react 应用内部,可以根据这些实例变量的值,控制执行逻辑

3、HostRootFiber 对象

  • 属于 react-reconciler 包,这是 react 应用中的第一个 fiber 对象,是 fiber 树的根节点,节点的类型是 HostRoot

这 3 个对象是 react 体系得以运行的基本保障,一经创建大多数场景下不会再销毁(除非卸载整个 root.unmount())

这一过程是从 react-dom 包发起的,内部调用了 react-reconciler 包,

创建 ReactDOM Root 对象

  1. 调用 ReacDOM.createRoot 创建 ReactDOMRoot 实例
  2. 调用 ReacDOMRoot 实例的 render 方法
  3. 调用 createRootImpl 创建的 fiberRoot 对象,将其挂载到 this._internalRoot 上
  4. 原型上有 render 和 unmount 方法,且内部会调用 updateContainer 进行更新

创建 fiberRoot 对象

无论那种模式下,再 ReactDOM(Blocking)Root 的创建过程中,都会调用一个相同的函数 createRootImpl,查看后续的函数调用,最后会创建 fiberRoot 对象

创建 HostRootFiber 对象

再 createFiberRoot 中,创建了 react 应用的首个 fiber 对象,称为 HostRootFiber(fiber.tag=HostRoot)

fiber 树中所有节点的 mode 都会和 HostRootFiber.mode 一致(新建的 fiber 节点,其 mode 来源与父节点),所以 HostRootFiber.mode 很重要,决定了以后整个 fiber 树构建过程

可中断渲染

可中断渲染(render 可以中断,部分生命周期函数有可能执行多次)

UNSAFE_componentWillMount,UNSAFE_componentWillReceiverProps 只有在 HostRootFiber.mode===ConcurrentRoot 才会开启

如果使用的是 legacy,即通过 ReactDOM.render(<App/>,dom)这种方式启动时 HostRootFiber.mode=NoMode,这种情况下无论是首次 render 还是后续 update 都只会进入同步工作循环,reconciliation 没有机会中断,所以生命周期只会调用一次

优先级管理

React 内部对于优先级的管理,贯穿运作流程的 4 个阶段,根据功能的不同,可以分为 3 种类型

  1. fiber 优先级(LanePriority):位于 react-reconciler 包,也就是 Lane(车道模型)
  2. 调度优先级(SchedulerPriority):位于 scheduler 包
  3. 优先级等级(ReactPriorityLevel):位于 react-reconciler 包中的 SchedulerWithReactIntegration.js,负责上述 2 套优先级体系的转换

Lane(车道模型)

  1. Lane 类型被定义为二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快
  2. Lane 是对于 expirationTime 的重构,以前使用 expirationTime 表示的字段,都改为了 lane

总结:

  • 可以使用的比特位一共有 31 位
  • 共定义了 18 中车道(Lane/Lanes)变量,每一个变量占有一个或者多个比特位,分别定义 Lane 和 Lanes 类型
  • 每一种车道都有对应的优先级
  • 占有地位比特位的 Lane 变量对应的优先级越高

Lane 的位运算

程序中的所有数在计算机内存中都是以二进制的形式储存的,位运算就是直接对整数在内存中的二进制位进行操作

位运算在 react 中的运用

优先级区别和联系

  1. LanePriority 和 SchedulerPriority 从命名上看,它们代表的是优先级
  2. ReactPriorityLevel:从命名上看,它代表的是等级而不是优先级,它用于衡量 LanePriority 和 SchedulerPriority 的等级

React 调度原理

在 react 原型过程中,调度中心位于 scheduler 包,是整个 react 运行时的中枢

  • react 两大循环:任务调度循环是主角
  • reconciler 运行流程:分为 4 个阶段,其中第 2 个阶段注册调度任务串联了 scheduler 包和 react-reconciler 包,其实就是任务调度循环中的一个任务(task)
  • 优先级管理:其中 Schedulerpriority 控制任务调度循环中的循环顺序

调度实现

调度中心最核心的代码在 SchedulerHostConfig.default.js 中

内核

该 js 文件一共导出了 8 个函数,核心逻辑就在函数中

在不同的 js 执行环境中,这些函数的实现会有区别,下面基于普通浏览器对函数分析

1、调度相关:请求或取消调度

  • requestHostCallback
  • cancelHostCallback
  • requestHostTimeout
  • cancelHostTimeout

这 4 个函数目的就是请求执行(或取消)回调函数

主要是及时回调,延时回调在 17.2 之后基本没用

任务队列管理

1、在 Scheduler.js 中,维护了一个 taskQueue,任务队列管理就是围绕这个 taskQueue 展开

  • taskQueue 是一个小顶堆数组
  • 源码中除了 taskQueue 队列之外还有一个 timerQueue 队列,这个队列是预留给演示任务使用的,在react@17.0.2版本里面,算是一个保留功能,没有用到

2、创建任务

  • 在 unstable_scheduleCallback 函数中

3、消费任务

  • 在下一时间循环中,最终执行 flushwork

fiber 树构建

在 react 运行时,fiber 树构造位于 react-reconciler 包,reconciler 的 4 个阶段如下:

  1. 输入阶段:衔接 react-dom 包,承接 fiber 更新请求
  2. 注册调度任务:与调度中心 scheduler 包交互,注册调度任务 task,等待任务回调
  3. 执行任务回调:在内存中构造出 fiber 树和 dom 对象,也是 fiber 树构造的重点内容
  4. 输出:与渲染器 react-dom 交互,渲染 DOM 节点

fiber 树构造处于第三个阶段,可以通过不同的视角来理解 fiber 树构造在 react 运行时中所处的位置

  • 从 scheduler 调度中心的角度来看,它是任务队列 taskQueue 中的一个具体的任务回调(task.callback)
  • 从 react 工作循环的角度来看,它属于 fiber 树构造循环

根据 react 运行的内存状态,分为两种情况

  1. 初次创建:在 react 应用首次启动时,界面还没有渲染,此时并不会进入对比过程,相当于直接构造一棵全新的树
  2. 对比跟新:react 应用启动后,界面已经渲染,如果再次发生更新,创建新 fiber 之前需要和旧 fiber 进行对比,最后构造的 fiber 树有可能是全新的,也可能是部分更新

ReacElement,Fiber,DOM 三者关系

1、ReacElement 对象(type 定义在 shared 包中)

  • 所有采用 jsx 语法书写的节点,都会编译器转换,最终会以 React.createElement(…)的方式,创建出来一个与之对应的 ReactElement 对象

2、fiber 对象(type 类型的定义在 ReactInternaIType.js 中)

  • fiber 对象是通过 ReactElement 对象进行创建的,多个 fiber 对象构成了一颗 fiber 树,fiber 树是构造 DOM 树的数据模型,fiber 树的任何改动,最后都体现到 DOM 树

3、DOM 对象:文档对象模型

  • DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合,也就是常说的 DOM 树
  • JavaScript 可以访问和操作存储在 DOM 中的内容,也就是操作 DOM 对象,进而触发 UI 渲染

双缓冲技术(double buffering)

在全局变量中 workInProgress,还有不少 workInProgress 来命名的变量,workInProgress 的应用实际上就是 React 的双缓冲技术

在 react 的上下文中,这种机制主要通过 fiber 架构实现,具体体现在维护两颗 fiber 树上:当前 fiber 树(curren tree)和工作中的树(work-in-progress tree,简称 WIP tree)

  • 当前 fiber 树:当前正在屏幕上展示的用户界面结构,每个 fiber 节点对应着 UI 中的一个组件或者 DOM 元素
  • 工作中的 fiber 树:当 react 需要对 UI 更新时,不是直接进行修改当前树,而是创建一个新的 fiber 树(WIP tree)。在这个树中,react 会对即将应用的变化进行计算和规划,包括哪些部分需要更新、添加或删除

工作流程:

  1. 更新调度:当 react 接收到状态变更或者 props 变更,它会开始在 WIP tree 上执行 reconciliation(协调)过程,对比新旧状态,找出最小的 DOM 操作
  2. 并发与分片:react fiber 运行更新过程在多个任务之间中断和恢复,着意味着复杂的更新可以被拆分成小块(time slicing),提高了 UI 的响应性,这是双缓冲在任务调度层面的体现
  3. 一次性渲染:一旦 WIP tree 构建完成并且所有必要的计算和优化都已完成,react 会将这颗树一次性地替换掉当前树,这个瞬间替换的过程很快,因为它实际上是对 DOM 树的一次整体更新,而非逐步修改,从而减少了页面重排和重绘的次数。

react 的双缓冲技术通过分离计划(构建 WIP terr)和执行(替换 current tree)阶段,实现了高效的 UI 更新管理,提示了用户体验。

优先级

在整个 react-reconciler 包中,Lane 的应用可以分为 3 个方面

update 优先级(update.lane)

在 react 体系中,有 2 种情况会创建 update 对象:

  1. 引用初始化:在 react-reconciler 包中的 updateContainer 函数中
  2. 发起组件更新,假设在 class 组件中调用 setState

requestUpdateLane:

返回一个合适的 update 优先级

  • legacy 模式:返回 SyncLane
  • concurrent 模式:
    正常情况下,根据当前的调度优先级来生成一个 lane
    特殊情况下(处于 suspense 过程中),会优先选择 TransitionLanes 通道中的空闲通道(如果所有 TransitionLanes 通道都被占用,就取最高优先级)

最后通过 scheduleUpdateOnFiber(current,lane,eventTime)函数,把 update.lane 正式带入到输入阶段

渲染优先级(renderLanes)

这是一个全局概念,每一次 render 之前,首先要确定本次 render 的优先级

无论是 legacy 还是 Concurrent 模式,在正式 render 之前,都会调用 getNextLanes 获取一个优先级

getNextLanes 会根据 fiberRoot 对象上的属性(expiredLanes,suspendedLanes,pingedLanes 等),确定出当前最紧急的 lanes

此处返回的 lanes 会作为全局渲染的优先级,用于 fiber 树构造过程中,针对 fiber 对象,或 update,只要它们的优先级(如:fiber.lanes 和 update.lane)比渲染优先级低,都将会被忽略

fiber 优先级

在 fiber 对象的数据结构中,其中有 2 个属性与优先级相关:

  1. fiber.lanes:代表本节点的优先级
  2. fiber.childLanes:代表子节点的优先级从 FiberNode 的构造函数中可以看出,fiber.lanes 和 fiber.childLanes 的初始值都为 NoLanes,在 fiber 树构造过程中,使用全局的渲染优先级(renderLanes)和 fiber.lanes 判断 fiber 节点是否更新
    如果全局的渲染优先级 renderLanes 不包括 fiber.lanes,证明该 fiber 节点没有更新,可以复用
    如果不能复用,进入创建阶段

栈帧模型

每次 fiber 树的构造是一个独立的过程,需要独立的一组全局变量,在 React 内部把这一个独立的过程封装为一个栈帧 stack(简单来说就是每次构造都需要独立的空间)

在进行 fiber 树构造之前,如果不需要恢复上一次的构造进度,都会刷新栈帧

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时&#xff0c;而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定&#xff0c;则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定&#xff0c;以便没有错误…

全网最详细金融APP测试功能点-测试用例,详细整理(全)

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…

mov文件怎么转换成mp4格式?这四种转换方法超级好用!

mov文件怎么转换成mp4格式&#xff1f;在数字娱乐的世界中&#xff0c;你是否曾遇到过MOV格式的视频&#xff1f;也许&#xff0c;对于许多人来说&#xff0c;这并不是一个常见的格式&#xff0c;但这并非偶然&#xff0c;首先&#xff0c;我们来谈谈MOV的兼容性问题&#xff0…

「漏洞复现」时空智友ERP系统updater.uploadStudioFile 任意文件上传漏洞

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

Python处理异常用操作介绍

Python中的异常处理主要用于捕获和处理程序运行过程中出现的错误。 在编写Python程序时&#xff0c;我们经常会遇到各种错误&#xff0c;如语法错误、运行时错误等。为了确保程序的稳定性和健壮性&#xff0c;我们需要对可能出现的错误进行捕获和处理。本文将介绍Python中常用的…

Python入门 2024/7/3

目录 for循环的基础语法 遍历字符串 练习&#xff1a;数一数有几个a range语句 三个语法 语法1 语法2 语法3 练习&#xff1a;有几个偶数 变量作用域 for循环的嵌套使用 打印九九乘法表 发工资案例 continue和break语句 函数的基础定义语法 函数声明 函数调用 …

MLLM QLoRA微调实战:基于最新的袖珍Mini-InternVL模型

引言 大型语言模型&#xff08;LLM&#xff09;的世界正在不断发展&#xff0c;新的进步正在迅速出现。一个令人兴奋的领域是多模态LLM&#xff08;MLLMs&#xff09;的发展&#xff0c;这种模型既能够理解文本又能够理解图像&#xff0c;并与之进行交互。因此&#xff0c;这种…

ICCV2023鲁棒性相关论文速览

Paper1 Towards Better Robustness against Common Corruptions for Unsupervised Domain Adaptation 摘要原文: Recent studies have investigated how to achieve robustness for unsupervised domain adaptation (UDA). While most efforts focus on adversarial robustnes…

udp发送数据如果超过1个mtu时,抓包所遇到的问题记录说明

最近在测试Syslog udp发送相关功能&#xff0c;测试环境是centos udp头部的数据长度是2个字节&#xff0c;最大传输长度理论上是65535&#xff0c;除去头部这些字节&#xff0c;可以大概的说是64k。 写了一个超过64k的数据(随便用了一个7w字节的buffer)发送demo&#xff0c;打…

Geotools系列说明之LineString仿高德航路截取说明

需求分析 我们在做webgl的时候经常会遇到这样的需求&#xff0c;计算给定航路的拥堵情况&#xff0c;不同的拥堵显示不同的颜色&#xff0c;航路截取计算等等。基于这类问题统一都可以使用LineString进行处理 实现思路 如上图所示&#xff0c;航路是几个关键的点然后练成线&a…

MySql Innodb 索引有哪些与详解

概述 对于MYSQL的INNODB存储引擎的索引&#xff0c;大家是不陌生的&#xff0c;都能想到是 B树结构&#xff0c;可以加速SQL查询。但对于B树索引&#xff0c;它到底“长”得什么样子&#xff0c;它具体如何由一个个字节构成的&#xff0c;这些的基础知识鲜有人深究。本篇文章从…

2本Top,4本纯正刊,25天即录!7月刊源表已更新!

本周投稿推荐 SCI • 能源技术类&#xff0c;1.5-2.0&#xff08;来稿即录25天&#xff09; • 计算机类&#xff0c;2.0-3.0&#xff08;纯正刊29天录用&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&a…

【微信小程序开发实战项目】——如何制作一个属于自己的花店微信小程序(2)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

Python基于决策树回归模型、多元线性回归模型、随机森林回归模型和LightGBM回归模型实现波士顿房价预测项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 城市住房市场的稳定与健康发展是衡量一个地区经济活力和社会福祉的重要指标之一。波士顿&#xff0c;作…

Three-pass authentication

7.2.3 Mechanism MUT.CR — Three-pass authentication # 参考符号 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7aed1610e49e48729933f8160e5228af.png)

研发驱动 再谱新篇丨美格智能南通研发中心正式成立

近日&#xff0c;美格智能全资设立的众格智能技术&#xff08;南通&#xff09;有限公司&#xff0c;正式在江苏省南通市紫琅科技城揭牌成立&#xff0c;此举也标志着继上海、西安、深圳之后&#xff0c;美格智能研发力量布局再谱新篇&#xff1a;美格智能南通研发中心正式成立…

工商业光伏项目如何快速开发?

一、前期调研与规划 1、屋顶资源评估&#xff1a;详细测量屋顶面积、承重能力及朝向&#xff0c;利用光伏业务管理软件进行日照分析和发电量预测&#xff0c;确保项目可行性。 2、政策与补贴研究&#xff1a;深入了解当地政府对工商业光伏项目的政策支持和补贴情况&#xff0…

KES数据库实践指南:探索KES数据库的事务隔离级别

并发控制 并发控制的重要性 并发控制是数据库管理系统中的一个核心概念&#xff0c;它确保在多用户环境中&#xff0c;对数据库的并发访问不会破坏数据的完整性和一致性。 当多个用户同时对数据库进行读写操作时&#xff0c;如果缺乏有效的并发控制机制&#xff0c;可能会导致数…

动态规划精品课 2024.6.26-24.7.3

一、斐波那契数列模型 0、第N个泰波那契数 class Solution {public int tribonacci(int n) {// 1. 创建 dp 表// 2. 初始化// 3. 填表// 4. 返回结果// 处理边界情况if (n 0)return 0;if (n 1 || n 2)return 1;int[] dp new int[n 1];dp[0] 0;dp[1] dp[2] 1;for (int i…

类型转换与数据绑定【Spring源码学习】

simpleTypeConverter 类型转换 SimpleTypeConverter typeConverter new SimpleTypeConverter(); Integer number typeConverter.convertIfNecessary("13",int.class); System.out.println(number);BeanWrapper 通过反射原理为bean赋值&#xff0c;走的是set方法…