We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
2021-05-06
想要知道 React 的内部运行机制,实际上就是要探索 React 如何将组件映射屏幕,以及组件中的状态发生了变化之后 React 如何将这些「变化」更新到屏幕上。
React
对于首次渲染,React 的主要工作就是将 React.render 接收到的 VNode 转化 Fiber 树,并根据 Fiber 树的层级关系,构建生成出 DOM 树并渲染至屏幕中。
React.render
VNode
Fiber
DOM
而对于更新渲染时,Fiber 树已经存在于内存中了,所以 React 更关心的是计算出 Fiber 树中的各个节点的差异,并将变化更新到屏幕中。
在进行流程解读之前,有一些关于 React 源码中的概念需要先了解一下。
为了实现 concurrent 模式,React 将渲染更新的过程分为了两个阶段:
concurrent
render
commit
JSX 会被编译转换成 React.createElement 函数的调用,其返回值就是 VNode,虚拟DOM 节点的描述对象。
JSX
React.createElement
虚拟DOM
在 React 源码中称之为 element,为了避免和 DOM元素 的冲突,这里我就用大家比较熟悉的 虚拟DOM 来称呼了。
element
DOM元素
{ // DOM节点名称或类组件、函数组件 type: 'div' | App, ref: null, key: null, props: null }
Fiber 有两层含义:程序架构、数据结构
从程序架构的角度来看,为了实现 concurrent 模式,需要程序具备的可中断、可恢复的特性,而之前 VNode 的树型结构很难完成这些操作,所以 Fiber 就应运而生了。
那 Fiber 究竟是如何实现可中断、可恢复的呢?这就要说说 Fiber 的具体数据结构了。
Fiber 是一个链表结构,通过 child、sibling、return 三个属性记录了树型结构中的子节点、兄弟节点、父节点的关系信息,从而可以实现从任一节点出发,都可以访问其他节点的特性。
child
sibling
return
除了作为链表的结构之外,程序运行时还需要记录组件的各种状态、实例、真实 DOM 元素映射等等信息,这些都会被记录在 Fiber 这个对象身上。
function FiberNode() { this.tag = tag this.key = key this.elementType = null this.type = null this.stateNode = null this.return = null this.child = null this.sibling = null this.index = 0 this.ref = null this.pendingProps = pendingProps this.memoizedProps = null this.updateQueue = null this.memoizedState = null this.dependencies = null this.mode = mode this.effectTag = NoEffect this.nextEffect = null this.firstEffect = null this.lastEffect = null this.expirationTime = NoWork this.childExpirationTime = NoWork this.alternate = null }
这三个属性主要用途是将每个 Fiber 节点连接起来,用链表的结构来描述树型结构的关系。
副作用标记,标识了此 Fiber 节点需要进行哪些操作,默认为 NoEffect。
NoEffect
标记了 NoEffect、PerformedWork 的节点在更新过程中会被跳过。
PerformedWork
// 源码位置:packages/shared/ReactSideEffectTags.js // 作为 EffectTag 的初始值,或者用于 EffectTag 的比较判断,其值为 0 表示没有副作用,也就是不涉 及更新 export const NoEffect = 0b000000000000 // 由 React devtools 读取, NoEffect 和 PerformedWork 都不会被 commit,当创建 Effect List时,会跳过NoEffect 和 PerformedWork export const PerformedWork = 0b000000000001 // 表示向树中插入新的子节点,对应的状态为 MOUNTING,当执行 commitPlacement 函数完成插入后, 清除该标志位 export const Placement = 0b000000000010 // 表示当 props、state、context 发生变化或者 forceUpdate 时,会标记为 Update ,检查到标记后,执行 mmitUpdate 函数进行属性更新,与其相关的生命周期函数为 componentDidMount 和 componentDidUpdate export const Update = 0b000000000100 export const PlacementAndUpdate = 0b000000000110 // 标记将要卸载的结点,检查到标记后,执行 commitDeletion 函数对组件进行卸载,在节点树中删除对应对 节点,与其相关的生命周期函数为 componentWillUnmount export const Deletion = 0b000000001000 export const ContentReset = 0b000000010000 export const Callback = 0b000000100000 export const DidCapture = 0b000001000000 export const Ref = 0b000010000000 export const Snapshot = 0b000100000000 export const Passive = 0b001000000000 export const LifecycleEffectMask = 0b001110100100 export const HostEffectMask = 0b001111111111
链表结构,保存了需要更新的后代节点,每个 Fiber 节点处理完自身后都会根据相应逻辑与父节点的 lastEffect 进行连接。
lastEffect
这样在 commit 阶段,只需要从根节点的 firstEffect 向下遍历,就可以将所有需要更新的节点进行相应处理了。
firstEffect
保存了同一事件循环中对组件的多次更新操作(多次调用 setState)
setState
tag 描述了 Fiber 节点的类型
tag
// 源码位置:packages/shared/ReactWorkTags.js export const FunctionComponent = 0 // 函数组件元素对应的 Fiber 结点 export const ClassComponent = 1 // Class组件元素对应的 Fiber 结点 export const IndeterminateComponent = 2 // 在不确定是 Class 组件元素还是函数组件元素时的取值 export const HostRoot = 3; // 对应 Fiber 树的根结点 export const HostPortal = 4 // 对应一颗子树,可以另一个渲染器的入口 export const HostComponent = 5 // 宿主组件元素(如div,button等)对应的 Fiber 结点 export const HostText = 6 // 文本元素(如div,button等)对应的 Fiber 结点 export const Fragment = 7
Fiber 节点的 stateNode 属性存储的当前节点的最终产物
stateNode
ClassComponent
Class
HostComponent
HostRoot
fiberRoot
fiberRoot 对象是整个 Fiber架构 的入口对象,其上记录了应用程序运行过程中需要保存的关键信息。
Fiber架构
function FiberRootNode(containerInfo, tag, hydrate) { this.tag = tag // current树 this.current = null // 包含容器 this.containerInfo = containerInfo this.pendingChildren = null this.pingCache = null this.finishedExpirationTime = NoWork // 存储工作循环(workLoop)结束后的副作用列表,用于commit阶段 this.finishedWork = null this.timeoutHandle = noTimeout this.context = null this.pendingContext = null this.hydrate = hydrate this.firstBatch = null }
containerInfo 保存了 React.render 函数第二个参数,也就是程序的真实 DOM 容器。
containerInfo
current 属性既是应用程序中 Fiber树 的入口。
current
Fiber树
current 的值是一个 HostRoot 类型的 Fiber 节点,这个 HostRoot 的子节点就是程序的根组件(App)对应的 Fiber 节点。
App
在首次渲染调用 React.render 时,应用程序中其实只有一个 HostRoot 的 Fiber 节点,而在 render 过程中,才会将我们传入的 App 组件构建成 HostRoot 的子 Fiber 节点。
双缓冲是指将需要变化的部分,先在内存中计算改变,计算完成后一次性展示给用户,这样用户就不会感知到明显的计算变化。离屏 Canvas 就是双缓冲的思想。
Canvas
对于 Concurrent 模式来说,更新计算的过程会被频繁中断,如果不使用缓冲技术,那用户就会感知到明显的中断变化。
Concurrent
每个 Fiber 节点的 alternate 属性会指向另一个 Fiber 节点,这个 Fiber 节点就是「草稿」节点,当需要进行计算时,就会在这个节点上进行。计算完成后将两个节点进行互换,展示给用户。
alternate
作为已经计算完成并展示到视图中的 Fiber 树,在源码中称为 current 树。
而 current 树的 alternate 指向的另一棵树,就是用来计算变化的,称为 WorkInProgress 树(WIP)。
WorkInProgress
WIP
函数或者是类,最终产出 VNode 和定义生命周期钩子。
类组件实例化后的对象,其上记录了生命周期函数、组件自身状态、响应事件等。对于函数组件来说,没有实例对象,所以在 hooks 出现之前函数组件不能拥有自己的状态,而在 hooks 之后,函数组件通过调用 hooks 的产生状态被记录在组件对应的 Fiber 对象中。
hooks
包含过期时间、更新内容的对象。
update 的集合,链表结构。React 的更新操作都是异步执行的,在同一个宏任务中执行的更新操作都会被记录在此处,统一在下一个队列中执行。
update
不管是首次渲染还是更新渲染,都一定会经过以下步骤:
所以我们先来了解一下什么是更新对象和队列。
主要是对同步的多次调用 setState 进行缓冲,避免冗余的渲染调用。
触发更新操作时,React 会从 this(类组件)或 hooks 返回的 setter 函数中找到对应的 Fiber 节点,然后根据传入 setState 的参数创建更新对象,并将更新对象保存在 Fiber 节点的 updateQueue 中。
this
setter
updateQueue
这样我们在同一个事件循环中对组件的多次修改操作就可以记录下来,在下一个事件循环中统一进行处理。处理时就会遍历 updateQueue 中的修改,依次合并获取最终的 state 进行渲染。
state
function createUpdate(expirationTime, suspenseConfig) { var update = { // 过期时间与任务优先级相关联 expirationTime: expirationTime, suspenseConfig: suspenseConfig, // tag用于标识更新的类型如UpdateState,ReplaceState,ForceUpdate等 tag: UpdateState, // 更新内容 payload: null, // 更新完成后的回调 callback: null, // 下一个更新(任务) next: null, // 下一个副作用 nextEffect: null, } { // 优先级会根据任务体系中当前任务队列的执行情况而定 update.priority = getCurrentPriorityLevel() } return update }
为了防止某个 update 因为优先级的问题一直被打断,React 给每个 update 都设置了过期时间(expirationTime),当时间到了就会强制执行改 update。
expirationTime
expirationTime 会根据任务的优先级计算得来
// 源码位置:packages/scheduler/src/Scheduler.js // 立即执行(可由饥饿任务转换),最高优先级 var ImmediatePriority = 1 // 用户阻塞级别(如外部事件),次高优先级 var UserBlockingPriority = 2 // 普通优先级 var NormalPriority = 3 // 低优先级 var LowPriority = 4 // 最低优先级,空闲时去执行 var IdlePriority = 5
简单点说,具有 UserBlockingPriority 级别的多个更新,如果它们的时间间隔小于 10ms,那么它们拥有相同的过期时间。
UserBlockingPriority
同样的方式可以推到出具有 LowPriority 级别的多个更新(一般为异步更新),如果它们的时间间隔小于 25ms,那么它们也拥有相同的过期时间。
LowPriority
React 的过期时间机制保证了短时间内同一个 Fiber 节点的多个更新拥有相同的过期时间,最终会合并在一起执行。
// 源码位置:packages/react-reconciler/src/ReactUpdateQueue.js function createUpdateQueue(baseState) { var queue = { // 当前的state baseState: baseState, // 队列中第一个更新 firstUpdate: null, // 队列中的最后一个更新 lastUpdate: null, // 队列中第一个捕获类型的update firstCapturedUpdate: null, // 队列中第一个捕获类型的update lastCapturedUpdate: null, // 第一个副作用 firstEffect: null, // 最后一个副作用 lastEffect: null, firstCapturedEffect: null, lastCapturedEffect: null, } return queue }
babel
VNode树
FiberRootNode
根Fiber
FiberRoot
effectTag
effectList
React 会先以 current 这个 Fiber 节点为基础,创建一个新的 Fiber 节点并赋值给 current.alternate 属性,然后在这个 alternate 节点上进行协调计算,这就是之前所说的 WIP 树。
current.alternate
协调时会在全局记录一个 workInProgress 指针,用来保存当前正在处理的节点,这样中断之后就可以在下一个事件循环中接着进行协调。
workInProgress
此时整个更新队列中只有 HostRoot 这一个 Fiber 节点,对当前节点处理完成之后,会调用 reconcileChildren 方法来获取子节点,并对子节点做同样的处理流程。
reconcileChildren
这里主要说一下三种主要节点:HostRoot、ClassComponent、HostComponent
简单思考一下,叶子节点必然是一个 DOM 类型的节点,也就是 HostComponent,所以对叶子节点的处理可以理解为将 Fiber 节点映射为 DOM 节点的过程。
当碰到叶子节点时,会创建相应的 DOM 元素,然后将其记录在 Fiber 的 stateNode 属性中,然后调用 appendAllChildren 将子节点创建好的的 DOM 添加到 DOM 结构中。
appendAllChildren
叶子节点处理完毕后
收集副作用的过程中主要有两种情况
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect
returnFiber.lastEffect.nextEffect = workInProgress
从根节点的 firstEffect 开始向下遍历
before mutation
getSnapshotBeforeUpdate
scheduleCallback
flushPassiveEffects
useEffect
mutation
rootWithPendingPassiveEffects
layout
componentDidMount
DidUpdate
至此整个 DOM 树就被创建并插入到了 DOM 容器中,整个应用程序也展示到了屏幕上,初次渲染流程结束。
context
shouldComponentUpdate
diff
Placement
Update
Deletion
当 key 和 type 都相同时,会复用之前的 Fiber 节点,否则则会新建并将旧节点标记删除。
key
type
在 Concurrent 模式下,任务以 Fiber 为单位进行执行,当 Fiber 处理完成,或者 shouldYield 返回值为 true 时,就会暂停执行,让出线程。
shouldYield
true
while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress) }
在 shouldYield 中会判断当前时间与当前切片的过期时间,如果过期了,就会返回 true,而当前时间的过期时间则是根据不同的优先级进行计算得来。
对于浏览器而言,如果我们想要让出 js 线程,那就是只能把当前的宏任务执行完成。等到下一个宏任务中再接着执行。当浏览器执行完一个宏任务后就会切换只渲染进程进行视图的渲染工作。MessageChannel 可以创建一个宏任务,其优先级比 setTimeout(0) 高。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
2021-05-06
想要知道
React
的内部运行机制,实际上就是要探索React
如何将组件映射屏幕,以及组件中的状态发生了变化之后React
如何将这些「变化」更新到屏幕上。React 渲染流程图解
对于首次渲染,
React
的主要工作就是将React.render
接收到的VNode
转化Fiber
树,并根据Fiber
树的层级关系,构建生成出DOM
树并渲染至屏幕中。而对于更新渲染时,
Fiber
树已经存在于内存中了,所以React
更关心的是计算出Fiber
树中的各个节点的差异,并将变化更新到屏幕中。React 中的基础概念
在进行流程解读之前,有一些关于
React
源码中的概念需要先了解一下。两个阶段
为了实现
concurrent
模式,React
将渲染更新的过程分为了两个阶段:render
阶段,利用双缓冲技术,在内存中构造另一颗Fiber
树,在其上进行协调计算,找到需要更新的节点并记录,这个过程会被重复中断恢复执行。commit
阶段,根据render
阶段的计算结果,执行更新操作,这个过程是同步执行的。VNode(元素)
JSX
会被编译转换成React.createElement
函数的调用,其返回值就是VNode
,虚拟DOM
节点的描述对象。在
React
源码中称之为element
,为了避免和DOM元素
的冲突,这里我就用大家比较熟悉的虚拟DOM
来称呼了。Fiber
Fiber 有两层含义:程序架构、数据结构
从程序架构的角度来看,为了实现
concurrent
模式,需要程序具备的可中断、可恢复的特性,而之前VNode
的树型结构很难完成这些操作,所以Fiber
就应运而生了。那
Fiber
究竟是如何实现可中断、可恢复的呢?这就要说说Fiber
的具体数据结构了。Fiber
是一个链表结构,通过child
、sibling
、return
三个属性记录了树型结构中的子节点、兄弟节点、父节点的关系信息,从而可以实现从任一节点出发,都可以访问其他节点的特性。除了作为链表的结构之外,程序运行时还需要记录组件的各种状态、实例、真实 DOM 元素映射等等信息,这些都会被记录在
Fiber
这个对象身上。return、child、sibling
这三个属性主要用途是将每个 Fiber 节点连接起来,用链表的结构来描述树型结构的关系。
effectTag(flags)
副作用标记,标识了此
Fiber
节点需要进行哪些操作,默认为NoEffect
。标记了
NoEffect
、PerformedWork
的节点在更新过程中会被跳过。nextEffect、firstEffect、lastEffect
链表结构,保存了需要更新的后代节点,每个
Fiber
节点处理完自身后都会根据相应逻辑与父节点的lastEffect
进行连接。这样在
commit
阶段,只需要从根节点的firstEffect
向下遍历,就可以将所有需要更新的节点进行相应处理了。updateQueue
保存了同一事件循环中对组件的多次更新操作(多次调用
setState
)Tag
tag
描述了Fiber
节点的类型stateNode
Fiber
节点的stateNode
属性存储的当前节点的最终产物ClassComponent
类型的节点则该属性指向的是当前Class
组件的实例HostComponent
类型的节点则该属性指向的是当前节点的DOM
实例HostRoot
类型的节点则该属性指向的是fiberRoot
对象FiberRootNode
fiberRoot
对象是整个Fiber架构
的入口对象,其上记录了应用程序运行过程中需要保存的关键信息。containerInfo
保存了React.render
函数第二个参数,也就是程序的真实DOM
容器。current
属性既是应用程序中Fiber树
的入口。current
的值是一个HostRoot
类型的Fiber
节点,这个HostRoot
的子节点就是程序的根组件(App
)对应的Fiber
节点。在首次渲染调用
React.render
时,应用程序中其实只有一个HostRoot
的Fiber
节点,而在render
过程中,才会将我们传入的App
组件构建成HostRoot
的子Fiber
节点。双缓冲
双缓冲是指将需要变化的部分,先在内存中计算改变,计算完成后一次性展示给用户,这样用户就不会感知到明显的计算变化。离屏
Canvas
就是双缓冲的思想。对于
Concurrent
模式来说,更新计算的过程会被频繁中断,如果不使用缓冲技术,那用户就会感知到明显的中断变化。每个
Fiber
节点的alternate
属性会指向另一个Fiber
节点,这个Fiber
节点就是「草稿」节点,当需要进行计算时,就会在这个节点上进行。计算完成后将两个节点进行互换,展示给用户。作为已经计算完成并展示到视图中的
Fiber
树,在源码中称为current
树。而
current
树的alternate
指向的另一棵树,就是用来计算变化的,称为WorkInProgress
树(WIP
)。组件
函数或者是类,最终产出
VNode
和定义生命周期钩子。组件实例
类组件实例化后的对象,其上记录了生命周期函数、组件自身状态、响应事件等。对于函数组件来说,没有实例对象,所以在
hooks
出现之前函数组件不能拥有自己的状态,而在hooks
之后,函数组件通过调用hooks
的产生状态被记录在组件对应的Fiber
对象中。update(更新对象)
包含过期时间、更新内容的对象。
updateList(更新队列)
update
的集合,链表结构。React
的更新操作都是异步执行的,在同一个宏任务中执行的更新操作都会被记录在此处,统一在下一个队列中执行。更新队列
不管是首次渲染还是更新渲染,都一定会经过以下步骤:
所以我们先来了解一下什么是更新对象和队列。
更新队列的作用
主要是对同步的多次调用
setState
进行缓冲,避免冗余的渲染调用。多次触发更新(setState)
触发更新操作时,
React
会从this
(类组件)或hooks
返回的setter
函数中找到对应的Fiber
节点,然后根据传入setState
的参数创建更新对象,并将更新对象保存在Fiber
节点的updateQueue
中。这样我们在同一个事件循环中对组件的多次修改操作就可以记录下来,在下一个事件循环中统一进行处理。处理时就会遍历
updateQueue
中的修改,依次合并获取最终的state
进行渲染。更新对象定义
为了防止某个
update
因为优先级的问题一直被打断,React
给每个update
都设置了过期时间(expirationTime
),当时间到了就会强制执行改update
。expirationTime
会根据任务的优先级计算得来简单点说,具有
UserBlockingPriority
级别的多个更新,如果它们的时间间隔小于 10ms,那么它们拥有相同的过期时间。同样的方式可以推到出具有
LowPriority
级别的多个更新(一般为异步更新),如果它们的时间间隔小于 25ms,那么它们也拥有相同的过期时间。React
的过期时间机制保证了短时间内同一个Fiber
节点的多个更新拥有相同的过期时间,最终会合并在一起执行。更新队列定义
初始渲染流程
JSX
定义会被babel
转换为React.createElement
的调用,其返回值为VNode树
。React.render
调用,实例化FiberRootNode
,并创建根Fiber
节点HostRoot
赋值给FiberRoot
的current
属性React.render
接受到的第一个参数VNode树
,将更新对象添加到HostRoot
节点的updateQueue
中HostRoot
节点开始遍历,在其alternate
属性中构建WIP
树,在构建Fiber
树的过程中会根据VNode
的类型进行组件实例化、生命周期调用等工作,对需要操作视图的动作将其保存到Fiber
节点的effectTag
上面,将需要更新在 DOM 上的属性保存至updateQueue
中,并将其与父节点的lastEffect
连接。commit
阶段,此阶段就是将effectList
收集的DOM
操作应用到屏幕上。commit
完成将current
替换为WIP
树。构建 WIP 树
React
会先以current
这个Fiber
节点为基础,创建一个新的Fiber
节点并赋值给current.alternate
属性,然后在这个alternate
节点上进行协调计算,这就是之前所说的WIP
树。协调时会在全局记录一个
workInProgress
指针,用来保存当前正在处理的节点,这样中断之后就可以在下一个事件循环中接着进行协调。此时整个更新队列中只有
HostRoot
这一个Fiber
节点,对当前节点处理完成之后,会调用reconcileChildren
方法来获取子节点,并对子节点做同样的处理流程。Fiber 节点处理
workInProgress
指针,作为下一个处理的节点。这里主要说一下三种主要节点:HostRoot、ClassComponent、HostComponent
HostRoot
主要是处理其身上的更新队列,获取根组件的元素。HostRoot
后会返回其child
节点,一般来说就是ClassComponent
了。Fiber
节点是需要进行组件实例化的,实例会被保存在Fiber
的stateNode
属性上。render
拿到其VNode
再次进行构建过程。VNode
,会使用sibling
属性将其相连。HostComponent
就是原生的DOM
类型了,会创建DOM
对象并保存到stateNode
属性上。叶子节点逻辑
简单思考一下,叶子节点必然是一个
DOM
类型的节点,也就是HostComponent
,所以对叶子节点的处理可以理解为将Fiber
节点映射为DOM
节点的过程。当碰到叶子节点时,会创建相应的
DOM
元素,然后将其记录在Fiber
的stateNode
属性中,然后调用appendAllChildren
将子节点创建好的的DOM
添加到DOM
结构中。叶子节点处理完毕后
workInProgress
指针指向其兄弟节点。workInProgress
指向其父节点。收集副作用
收集副作用的过程中主要有两种情况
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect
returnFiber.lastEffect.nextEffect = workInProgress
处理副作用
从根节点的
firstEffect
开始向下遍历before mutation
:遍历effectList
,执行生命周期函数getSnapshotBeforeUpdate
,使用scheduleCallback
异步调度flushPassiveEffects
方法(useEffect
逻辑)mutation
:第二次遍历,根据Fiber
节点的effectTag
对DOM
进行插入、删除、更新等操作;将effectList
赋值给rootWithPendingPassiveEffects
layout
:从头再次遍历,执行生命周期函数,如componentDidMount
、DidUpdate
等,同时会将current
替换为WIP
树,置空WIP
树;scheduleCallback
触发flushPassiveEffects
,flushPassiveEffects
内部遍历rootWithPendingPassiveEffects
渲染完成
至此整个
DOM
树就被创建并插入到了DOM
容器中,整个应用程序也展示到了屏幕上,初次渲染流程结束。更新渲染流程
setState
触发更新,React
通过this
找到组件对应的Fiber
对象,使用setState
的参数创建更新对象,并将其添加进Fiber
的更新队列中,然后开启调度流程。Fiber
节点开始构建WIP
树,此时会重点处理新旧节点的差异点,并尽可能复用旧的Fiber
节点。Fiber
节点,检查Fiber
节点的更新队列是否有值,context
是否有变化,如果没有则跳过。state
,调用shouldComponentUpdate
判断是否需要更新。render
方法获取VNode
,进行diff
算法,标记effectTag
,收集到effectList
中。Placement
DOM
元素,判断属性是否发生变化,标记Update
Deletion
effectList
,调用生命周期并更新DOM
。Fiber Diff
单个节点
当
key
和type
都相同时,会复用之前的Fiber
节点,否则则会新建并将旧节点标记删除。多个节点
任务与调度
时间切片
在
Concurrent
模式下,任务以Fiber
为单位进行执行,当Fiber
处理完成,或者shouldYield
返回值为true
时,就会暂停执行,让出线程。在
shouldYield
中会判断当前时间与当前切片的过期时间,如果过期了,就会返回true
,而当前时间的过期时间则是根据不同的优先级进行计算得来。与浏览器通信 - MessageChannel
对于浏览器而言,如果我们想要让出 js 线程,那就是只能把当前的宏任务执行完成。等到下一个宏任务中再接着执行。当浏览器执行完一个宏任务后就会切换只渲染进程进行视图的渲染工作。MessageChannel 可以创建一个宏任务,其优先级比 setTimeout(0) 高。
参考资料
The text was updated successfully, but these errors were encountered: