# React 设计模式读书笔记
# 第1章
UI= f(state)
。框架内部运行机制根据当前状态渲染视图
- 根据自变量state变化计算出UI变化
- 根据UI变化执行具体的宿主环境API
副作用包括,调用DOM API,I/O操作,控制台打印等“函数调用过程中产生的,外部可观察的变化”都属于副作用。
前端框架可以分为三类
- 应用级框架 React
- 组件级框架 Vue
- 元素级框架 Svelte
useRef,用于在组件多次render之间缓存一个“引用类型的值”。
细粒度更新 在Vue和Mobx中使用的“能自动追踪依赖的技术”被称为“细粒度更新”。
useState
function useState(value) {
// 保存了依赖了改state的effect
const subs = new Set()
const getter = () => {
// 获取当前上下文的effect
const effect = effectStack[effectStack.length - 1]
if(effect) {
// 建立订阅关系
subscribe(effect,subs)
}
return value
}
const setter = (nextValue) => {
value = nextValue
//通知所有订阅改state的effect执行
for(const effect of [...subs]) {
effect.execute()
}
}
return [getter,setter]
}
function subscribe(effect, subs) {
subs.add(effect)
effect.deps.add(subs)
}
const [age] = useState(1)
const [sex] = useState(2)
useEffect(() => {
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
useEffect
- useEffect调用后,回掉函数立即执行
- 依赖的自变量变化后,回调函数立即执行
- 不需要显示指定依赖
function useEffect(callback) {
const execute = () => {
// 重置依赖
cleanup(effect)
// 将当前effect推入栈顶, 在useState的getter内部获取effectStack的栈顶effect及为“当前effect上下文”
effectStack.push(effect)
}
try{
// 执行回调
callback()
}finally{
// effect 出栈
effectStack.pop()
}
const effect = {
execute,
deps: new Set() // 该effect 依赖的state.subs
}
// 立即执行一次,建立订阅发布关系
execute()
}
function cleanup(effect) {
// 从该effect订阅的所有state对应subs中移除该effect
for(const subs of effect.deps) {
subs.delete(effect)
}
effect.deps.clear()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function useMemo(callback){
const [s,set] = useState(callback)
useEffect(() => set(callback()))
return s
}
2
3
4
5
举例
const [name1,setName1] = useState('hello')
const [name2,setName2] = useState('wolrd')
const [showAll, triggerShowAll] = useState(true)
const whoIsHere = useMemo(() => {
if(!showAll()){
return name1()
}
return `${name1()} 和 ${name2()}`
})
useEffect(() => console.log('谁',whoIsHere()))
useEffect(() => {
console.log('我是',name1())
})
setName1('小明')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1.2.2 AOT
- AOT提前编译或预编译,宿主环境获取的是编译后的代码。
- 编译时可以检测错误
- 首次运行笔JIT快
- 代码体积小于JIT
- JIT即时编译,代码在宿主环境中编译并执行,运行时检测错误
Vue3的编译优化,在编译时可以标记模版语法为静态部分与动态部分。
❗️❗️React Forget❗️❗️
# Virtual DOM
vdom是实现“根据自变量变化计算出UI变化”的一种主流技术,工作原理如下
- 将“元素描述的UI”转为“VDOM描述的UI” 。比如vue中的render,react中的createElement
- 对比变化前后“VDOM描述的UI”。计算出UI中发生变化的部分。比如vue中的patch,react中的fiberNode
优点如下
- 相比较DOM的体积优势,dom包含大量冗余的属性。使用“包含较少冗余属性的vdom”,能减少内存开销
- 相较于AOT更强大的描述能力。
- 多平台渲染多抽象能力
# 第2章 React理念
React是一款“重运行时”的应用级框架。
CPU瓶颈,I/O瓶颈
如果js执行时间过长,导致渲染流水线绘制图片的速度跟不上屏幕刷新频率,就会造成页面掉帧。表现为页面卡顿,这就是造成CPU瓶颈的原因
react使用时间切片技术来解决这个问题,将vdom的执行过程拆分为一个个独立的宏任务,将每个宏任务的执行时间限制在一定范围内(5ms)。
I/O瓶颈对于前端来说主要是网络延迟。
- 优先响应人机交互,为不同的操作赋予不同的优先级
- 所有优先级统一调度,优先处理“最高优先级的更新”
- 如果当前更新,有“更高优先级的更新”产生,则中断当前更新,优先处理高优先级更新
要实现上述3个要点,需要React底层实现:
- 用于调度优先级的调度器
- 用于调度器的调度算法
- 支持可中断的VDOM实现
时间切片, V16及以上都是使用新架构,旧架构不能无法实现Time slice
新架构
- Reconciler(协调器)- VDOM的实现,负责根据自变量变化计算出UI的变化
- Renderer(渲染器)- 负责将变化的UI渲染到宿主环境
- Scheduler(调度器) - 调度任务的优先级,高优先级任务优先进入Reconciler
新架构中Reconciler中的更新流程从递归变成了“可中断的循环过程”。每次循环都会调用shouldYield判断当前Time Slice是否有剩余时间,没有剩余时间则暂停更新流程,将主线程交给渲染流水线,等待下一个宏任务再继续执行,这就是Time Slice的实现原理。
function workLoopConcurrent() {
// 一直执行任务,直到任务执行完成中断
while(workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress)
}
}
function shouldYield() {
// 当前时间是否大于过期时间
// 其中deadline = getCurrentTime() + yieldInterval
// yieldInterval为调度器的预设时间间隔,默认是5ms
return getCurrentTime() >= deadline
}
2
3
4
5
6
7
8
9
10
11
12
13
当Scheduler将调度后的任务交给Reconciler后,Reconciler最终会为VDOM元素标记各种副作用flags。 比如移动,插入,删除。
Scheduler和Reconciler的工作都是在内存中进行。只有Reconciler完成工作后,才会进入Renderer
Renderer根据“Reconciler标记各种副作用flags”执行对应的操作。
# 2.2.2 主打特性的迭代
React大体经历了4个发展时期
- Sync(同步) - 旧架构
- Async Mode(异步模式)- 新架构
- Concurrent Mode(并发模式)- 新架构
- Concurrent Feature(并发特性)- 新架构
CPU瓶颈和I/O瓶颈,并不是同时解决的。解决CPU瓶颈的方式是“架构重构”。 重构后Reconciler的工作流由“同步”变为“异步,可中断”。因此这一个时间的React被称为Async Mode。
单一更新的工作流程变为“同步、可中断”并不能解决I/O瓶颈,解决问题的关键在于“使多个更新的工作流程并发执行”。所以继续迭代为Concurrent Mode(并发模式)。
React渐进升级
- Legacy模式,通过
ReactDOM.render(<APP/>, rootNode)
创建的应用遵循该模式。默认关闭StrictMode。表现未开启并发更新 - Blocking模式。通过
ReactDOM.creatBlockingRoot(rootNode).render(<App/>)
创建的应用遵循该模式,作为从Legacy向Concurrent过度的中间模式,默认开启StrictMode,表现未开启并发更新,但是启用了一些新功能,(比如AutomaticBatching) - Concurrent模式,通过ReactDOM.createRoot(rootNode).render(
)创建的应用遵循该模式,默认开启StrictMod,表现为开启并发特性
三种模式特性对比图
Snipaste_2023-11-04_21-59-31.png
React在18中不再提供三种开发模式,而是以“是否使用并发特性”作为“是否开启并发更新”的依据。具体说就是,开发者在v18中统一使用ReactDOM.createRoot创建应用。当不使用并发特性时,表现为未开启并发更新,但是启用了一些新功能,(比如AutomaticBatching)。当使用并发特性后表现为开启并发特性
# 2.3 Fiber架构
Fiber架构是新架构的基础。React中有三种节点类型
- React Element(React元素),即createElement方法的返回值
- React Component(React 组件),开发者可以在React中定义函数,类两种类型的Component
- FiberNode,组成Fiber架构的节点类型。
// React Component
const App = () => {
return <h3>1</h3>
}
// React Element
const ele = <App/>
// 在React运行时内部,包含App对应FiberNode
ReactDOM.createRoot(rootNode).render(ele)
2
3
4
5
6
7
8
9
10
# 2.3.1 FiberNode的含义
FiberNode包含以下三次含义
- 作为架构,v15的Reconciler采用递归的方式执行,被称为Stack Reconciler。v16及以后版本的Reconciler基于FiberNode实现,被称为Fiber Reconciler
- 作为“静态的数据结构”,每个FiberNode对应一个React元素,用于保存React元素的类型,对应的DOM元素等信息
- 作为“动态的工作单元”,每个FiberNode用于保存“本次更新中该React元素变化的数据,要执行的工作(增删改查,更新ref,副作用等)”
双缓存,Fiber架构中同时存在两颗Fiber Tree,一颗是“真实UI对应的Fiber Tree”,可以理解为前缓冲区,另一颗是“正在内存中构建的Fiber Tree”,可以理解为后缓冲区。
current指“前缓冲区中的FiberNode”,workInProgress指“后缓冲区的FiberNode”,alternate指向“另一个缓冲区中对应的FiberNode” current.alternate === workInProgress workInProgress.alternate === current
# 2.3.3 mount时Fiber Tree的构建
moute时有2个情况
- 整个应用的首次渲染,这种情况发生在首次进入页面时
- 某个组件的首次渲染,当isShow为true时,Btn组件进入mount流程,此时Btn组件所在应用可能处于update流程
{isShow ? <Btn/> :null}
整个应用的首次渲染
- 创建fiberRootNode(情况2没有)
- 创建tag为3的FiberNode,代表HostRoot,后文称该FiberNode为HostRootFiber(情况2没有)
- 从HostRootFiber开始,以DFS深度优先搜索的顺序生成FiberNode
- 在遍历过程中,为FiberNode标记“代表不同副作用的flags”,以便后续在Renderer中使用
HostRoot代表“应用在宿主环境挂载的根节点”,也就是“rootElement” HostRootFiber 代表“HostRoot对应的FiberNode” FiberRootNode负责管理该应用的全局事宜,比如
- Current Fiber Tree与 Wip Fiber Tree之间的切换
- 应用中任务的过期时间
- 应用的任务调度信息
fiberRootNode.current === HostRootFiber
function App() {
const [num, add] = useState(0)
return <p onClick={() => add(num + 1)}>{num}</p>
}
const rootElement = document.getElementById('root')
ReactDOM.createRoot(rootElement).render('<App/>')
2
3
4
5
6
7
8
9
10
fixtures: 包含一些为开发者准备的小型React测试项目
packages: 所有与React相关的包存放于此
- react-art,对应Canvas,SVG,VML
- react-dom,对应浏览器和ssr
- react-native-renderer,用于Native环境
- react-noop-renderer,用户debug Fiber
- react-test-renderer,渲染成js对象,用于测试
- react-server,创建自定义ssr流
- react-client,自定义的流数据模型
- react-interactions,用于测试交互相关的内部特性,比如react的事件模型
- react-reconciler,即Fiber Reconciler
- react-fetch用于数据请求
- react-is 用于测试“组件是否时某中类型”
- react-refresh,用于在react中使用fast refresh(类似热重载,在运行时调试 React component不会丢失状态)
- create-subscription,用于在组件内订阅React外部数据源
scripts: 各种工具链脚本,比如git,jest ,eslint
# 第3章 render阶段
Reconciler工作的阶段在React内部被称为render阶段,classComponent的render函数,Function Component函数本身都在该阶段被调用
performSyncWorkOnRoot 同步更新流程 performConcurrentWordOnRoot 并发更新流程
function workLoopSync() {
while(workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
function workLoopConcurrent() {
while(workInProgress !== null && !shouldYield()) {
perforUnitOfWork(workInProgress)
}
}
2
3
4
5
6
7
8
9
10
11
function beginWork(current,workInProgress,renderLanes) {
if (current !== null) {
// 执行update操作
} else {
// 执行mount
}
// 根据tag不同,进入不同处理逻辑
switch (workInProgress.tag) {
case IndeterminateComponent // FC mount 时进入的分支,
case LazyComponent
case FunctionComponent // FC update时进入
case ClassComponent
case HostRoot
case HostHoistable
case HostComponent // 代表原声Element类型(比如DIV SPAN)
...
...
}
}
Reconciler 核心代码
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
){
if (current === null) {
// mount时
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
export const reconcileChildFibers: ChildReconciler = createChildReconciler(true);
export const mountChildFibers: ChildReconciler = createChildReconciler(false);
function createChildReconciler(
shouldTrackSideEffects: boolean, // 代表是否追踪副作用,即是否标记flags
): ChildReconciler {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 3.3.3 位运算在“标记状态”中的应用
export const NoContext = /* 未处于React上下文 */ 0b000;
const BatchedContext = /* 处于batchedUpdates上下文 */ 0b001;
export const RenderContext = /* 处于render阶段 */ 0b010;
export const CommitContext = /* 处于commit阶段 */ 0b100;
当执行流程进入render阶段,会使用按位或标记进入对应的上下文
let executionContext = NoContext
executionContext |= RenderContext
此时可以结合按位与和NoContext来判断“是否处于某一上下文中”
(0b010 & 0b010) !== 0b000
(executionContext & RenderContext) !== NoContext // true
(executionContext & CommitContext) !== NoContext // false
从当前上下文中移除 RenderContext 上下文,结合按位与,按位非移除标记
executionContext &= ~RenderContext
0b000 0000 0000 0000 0000 0000 0000 0010 RenderContext
对RenderContext按位非
0b111 1111 1111 1111 1111 1111 1111 1101 ~RenderContext
上述数据执行按位与 得到
0b000 0000 0000 0000 0000 0000 0000 0000
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 3.4 completeWork
completeWork也会根据wip.tag 区分对待,流程大概包括2个
- 创建或者标记元素更新
- flags冒泡
beginWork的reconcilerChildFibers方法用来“标记fiberNode的插入,删除,移动”。
completeWork方法的步骤1会完成 “beginWork做的标记”。当步骤1完成后,该fiberNode在本次更新中的增删改操作均已标记完成。至此,Reconciler中“标记flags”相关工作基本完成。但是距离在Renderer中间“解析flags”还有一项重要工作,就是 ”flags冒泡“
当流程经过Reconciler后,会得到一个Wip Fiber Tree,部分fiberNode被标记flags。 Renderer需要对“被标记的fiberNode对应的DOM元素”执行“flags对应的DOM操作”。那么如何高效的找到这些散落在 Wip Fiber Tree各处的“被标记的fiberNode”,这就是flags冒泡的作用。
我们知道,completeWork属于归的阶段。从叶子元素开始,流程自下而上,fiberNode.subtreeFlags记录了 该fiberNode的“所有子孙fiberNode上被标记的flags”,每个fiberNode经过如下操作,可以将子孙fiberNode中 “标记的flags”向上冒泡一层
let subtreeFlags = NoFlags
// 收集子fiberNode的子孙fiberNode中标记的flags
subtreeFlags |= child.subtreeFlags
// 收集子fiberNode标记的flags
subtreeFlags |= child.flags
// 附加在当前fiberNode的subtreeFlags上
completeWork.subtreeFlags |= subtreeFlags
2
3
4
5
6
7
8
9
10
11
12
13
completeWork 在mount时的流程总结如下
- 根据wip.tag进入不同处理分支
- 根据current!=null区分是mount还是update
- 对于HostComponent,首先执行createInstance方法创建对应的DOM元素
- 执行appendAllChildren将下一级DOM元素挂载在步骤(3)创建的DOM元素
- 执行finalizeInitialChildren完成属性初始化
- 执行bubbleProerties完成flags冒泡
# 3.4.3 update概览
update流程将完成标记属性的更新。主要是diffProperties。包括
- 第一次遍历,标记删除“更新前有,更新后没有”的属性
- 第二次遍历,标记更新”update流程前后发生改变“的属性
diffProperties,所有变化属性的key,value会保存在fiberNode.updateQueue中,同时,该fiberNode会标记Update
workInProgress.flags |= Update
在updateQueue中,数据以key,value作为数组的相邻两项。如下
['title','1','style',{'color':"red"}]
2
3
# 第4章 commit 阶段
Renderer工作的阶段被称为commit阶段,在commit阶段,会将各种副作用(flags标记)commit(提交)到宿主环境的Ui中。
render阶段流程可能被打断,而commit阶段一旦开始就会同步执行直到完成。整个阶段可以分为3个阶段
- BeforeMutaion阶段
- Mutation 阶段
- Layout 阶段
commit阶段的起点开始于commitRoot方法的调用
commitRoot(root);
- root代表“本次更新所属FiberRootNode”
- root.finishedWork代表Wip HostRootFiber,即“render阶段构建的 Wip FIber Tree”的HostRootFiber
在三个子阶段执行前,需要判断本次更新是否涉及“与三个子阶段相关的副作用”
// subtreeHasEffects 代表Wip HostRootFiber的子孙元素存在的副作用flags
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
// rootHasEffect 代表 Wip HostRootFiber 本身存在的副作用flags。
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
// 进入三个阶段
} else {
// 省略本次更新没有三个子阶段的副作用
}
export const BeforeMutationMask: number =
// TODO: Remove Update flag from before mutation phase by re-landing Visibility
// flag logic (see #20043)
Update |
Snapshot |
(enableCreateEventHandleAPI
? // createEventHandle needs to visit deleted and hidden trees to
// fire beforeblur
// TODO: Only need to visit Deletions during BeforeMutation phase if an
// element is focused.
ChildDeletion | Visibility
: 0);
export const MutationMask =
Placement |
Update |
ChildDeletion |
ContentReset |
Ref |
Hydrating |
Visibility;
export const LayoutMask = Update | Callback | Ref | Visibility;
// TODO: Split into PassiveMountMask and PassiveUnmountMask
export const PassiveMask = Passive | Visibility | ChildDeletion;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
They're used by React Dev Tools.
Placement:当前fiberNode或子孙fiberNode存在“需要插入或移动的HostComponent或HostText”
Hydrating:hydrate相关
Update classComponet存在更新,且定义了componentDidMount或者componentDidUpdate方法;HostComponent发生属性变化;HostText发生内容变化;Fc定义了useLayoutEffect
Skipped value
- ChildDeletion有“需要被删除的子HostComponent或子HostText”
- ContentReset 清空HostComponent的文本内容
- Callback:当calssComponent中的this.setState执行时,或ReactDOM.render执行时传递了回调函数参数
Used by DidCapture
- Ref:HostComponent ref属性的创建与更新
- Snapshot;ClassComponent存在更新,且定义了getSnapshotBeforeUpdate方法
- Passive :Fc中定义了useEffect且需要触发回调函数
Used by Hydrating
- Visibility:控制SuspenseComponent的子树与fallback切换时子树的显隐
# 第5章 schedule阶段
Scheduler为“Time Slice分割出的一个个短宏任务”提供了执行的驱动力
为了更灵活的控制宏任务的执行时机,React实现了一套基于lane模型的优先级算法, 并基于这套算法实现了Batched Updates(批量更新),任务打断/恢复机制等低级特性。 基于低级特性,React实现了“面向开发者”的高级特性(也叫并发特性),比如Concurrent Suspense ,useTransition
Scheduler预设了5中优先级,优先级依次降低
- ImmediatePriority(最高优先级,同步执行)
- UserBlockingPriority
- NormalPriority
- LowPriority
- IdleOriority(最低优先级)
scheduleCallback(LowPriority, fn)
执行上述函数后返回
task = {
expirationTIme: startTime + timeout,
callback:fn
}
expirationTIme 由scheduleCallback执行时的时间 + timeout(不同优先级对应的timeout)
Scheduler将expirationTIme字段作为“task之间排序的依据”,值越小优先级越高,高优先级的task.callback在新的宏任务重优先执行。这就是Scheduler调度的原理
2
3
4
5
6
7
8
9
10
11
12
Scheduler简易实现
// 保存所有work
const workList = []
// 上一次执行perform的work对应优先级
let pervPriority = IdlePriority
// 当前调度的callback
let curCallback
function schedule(params) {
// 获取“当前正在调度的callback”
const cbNode = getFirstCallbackNode()
// 取出最高优先级的工作
const curWork = workList.sort((w1,w2) => {
return w1.priority - w2.priority;
})[0]
// 以下直到“赋值curCallback”之前都是策略逻辑
if(!curWork) {
// 没有work需要调度,返回
curCallback = null
cbNode && cancelIdleCallback(cbNode)
return
}
// 获取当前最高优先级work的优先级
const {priority:curPriority} = curWork
if(curPriority === prevPriority) {
// 如果优先级相同,则不需要调度,退出调度
return
}
// 准备调度当前最高优先级的work
// 调度之前,如果有工作在进行,则中断它
cbNode && cancelIdleCallback(cbNode);
// 调度当前最高优先级的work
curCallback = scheduleCallback(curPriority,perform.bind(null,curWork))
}
function perform(work,didTimeout) {
// didTimeout 解决饥饿问题不断的插入高优先级任务,导致低优先任务无法执行
const needSync = work.priority === ImmdiatePriority || didTimeout;
// 同步执行 ,如果
while((needSync || !shouldYield()) && work.count) {
work.count --
inserItem()
}
// 跳出循环,prevPriority 代表上一次执行的优先级
prevPriority = work.priority
if(!work.count) {
// 从workList 中删除完成的work
const workIndex = workList.indexOf(work)
workList.splice(workIndex, 1)
// 重置优先级
prevPriority = IdlePriority
}
const pervCallback = curCallback
// 调度完成后,如果callback发生变化,代表这是新的work
schedule()
const newCallback = curCallback
if(newCallback && pervCallback === newCallback) {
// callback不变,代表同一个work,只不过Time Slice时间用尽
// 返回的函数会被Scheduler继续调用
return perform.bind(null,work)
}
}
// 移除task
function cancelCallback(task) {
task.callback = null
}
// 用于“以某一优先级调度callback”
function scheduleCallback(){
}
// 用于“提示Time Slice时间是否用尽”,作为中断循环的一个依据
function shouldYield() {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
注意 delay 和 expirationTime
delay代表task需要延迟执行的时间,在scheduleCallback方法中配置,“配置delay后的task”会先进入 timerQueue中。当delay对应的时间后,task会从timerQueue中取出并移入taskQueue中。
expirationTime代表“task的过期时间”,不是所有的task都会有delay。没有配置delay的task 直接进入taskQueue
expirationTime用于解决饥饿问题,也会用这个对task进行排序
使用小顶堆堆数据结构实现优先级队列,push和pop的时间复杂度是O(log n),peek操作的时间复杂度是O(1)
小顶堆特点:
- 是一个完全二叉树,(初最后一层外,其他层的节点个数都是满的,且最后一层节点靠左排列)
- 堆中每一个节点的值都小于等于其子树的每一个值。
宏任务的选择 requestIdleCallback问题
- 兼容问题
- 执行频率不稳定,受很多因素影响,比如切换tab后,之前tab注册的rIC执行的频率会大幅下降
- 应用场景局限
rIC的设计初衷是“能够在事件循环中执行低优先级工作”,减少对动画,用户输入等高优先级事件等影响。这意味着rIC 的应用场景被局限在“低优先级工作”中,这与Scheduler中“多中高优先级的调度策略不符合”
let schedulePerformWorkUntilDeadline
if(typeof localSetImmediate === 'function'){
// 使用setImmediate(performWorkUntilDeadine)
schedulePerformWorkUntilDeadline = function() {
localSetImmediate(performWorkUntilDeadline)
}
} else if(typeof MessageChannel !== 'undefined') {
// 使用MessageChannel实现
let channel = new MessageChannel()
let port = channel.port2
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = function() {
port.postMessage(null)
}
} else {
// 使用setTimeout实现
schedulePerformWorkUntilDeadline = function() {
localsetTimeout(performWorkUntilDeadline, 0)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.3 lane模型
由于React与Scheduler的优先级并不通用,因此React选出优先级提交给Scheduler前会进行转换。 Scheduler的5种优先级
- NoPriority = 0
- ImmdiatePriority = 1
- UserBlockingPriority = 2
- NormalPriority = 3
- LowPriority = 4
- IdlePriority = 5
Scheduler并不与React共用一套优先级体系,React有4种优先级
- DiscreteEventPriority = SyncLane 离散事件的优先级 ,比如click,input,focus,blur,touchstart等
- ContinuousEventPriority = InputContinuousLane 连续事件的优先级 ,比如drag,mousemove,scroll, touchmove,wheel等
- DefaultEventPriority = DefaultLane 对应默认的优先级,比如通过计时器周期性触发更新。这种情况产生的update不属于“交互产生的update”,所以优先级是默认的优先级
- IdleEventPriority = IdleLane,对应空闲情况的优先级
从react到Scheduler,优先级需要经过如下2次转会
- 将lanes转会EventPriority,涉及到方法如下,经过转换,返回到是EventPriority
function lanesToEventPriority(lanes) {
// 获取lanes中优先级最高的lane
let lane = getHighestPriorityLane(lanes);
// 如果优先级高于DiscreteEventPriority,返回DiscreteEventPriority
if(!isHighrEventPriority(DiscreteEventPriority,lane)) {
return DiscreteEventPriority
}
// 如果优先级高于ContinuousEventPriority,返回ContinuousEventPriority
if(!isHighrEventPriority(ContinuousEventPriority,lane)) {
return ContinuousEventPriority
}
// 如果包含 非Idle的任务,返回DefaultEventPriority
if(includesNonIdleWork(lane)) {
return DefaultEventPriority
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 将EventPriority转化为Scheduler优先级,具体逻辑如下
let schedulerPriorityLevel;
// lanes转化为EventPriority
switch(lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmdiatePriority
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingPriority
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalPriority
break;
case IdleEventPriority:
schedulerPriorityLevel = IdlePriority
break;
default:
schedulerPriorityLevel = NormalPriority
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22