fiber架构大体
fiberRoot对象:是整个Fiber树的入口(使用FiberRootNode构造函数实例化fiberRoot对象,使用FiberNode构造函数实例化Fiber树的第一个结点对象)
stateNode属性:存储了当前结点对应的DOM实例(用来更新到屏幕上)
总体:
React通过时间分片的方式来处理一个或多个Fiber 结点的更新任务,每次更新Fiber结点时会向调度器请求任务执行权
渲染流程三个阶段:
prerender阶段(构建fiberRoot对象,只在首次渲染)
实例化fiberRoot 对象,创建(整个应用程序的)更新队列(updateQueue)并添加到 fiberRoot 对象上
(使用FiberRootNode构造函数实例化fiberRoot对象,使用FiberNode构造函数实例化Fiber树的第一个结点对象)
render阶段(构建workInprogres树、收集副作用)
- 每次更新Fiber结点时会向调度器请求任务执行权(任务调度器为其分配任务执行优先级),才能走到render阶段。
- 得到任务执行权后,React 将每个 Fiber 结点作为最小工作单位,通过自顶向下逐个遍历Fiber结点,构建workInProgress树(一颗新的Fiber树,更新的计算、调用部分生命周期函数等会在这个过程中完成)。这一过程总是从顶层的HostRoot结点开始遍历,直到找到未完成工作或者需要处理的结点。
- render阶段执行完成后,FiberRoot对象上面的current属性指向了一颗Fiber树(current 树),current 树上面的lternate属性指向了另一颗Fiber树(workInProgress树),这两颗Fiber树通过alternate属性形成了一个闭环。
- render阶段执行完成后也会得到副作用列表Effect List(就是那些需要更新的Fiber结点集合,一般是workInProgress树的子集)
commit阶段(把effect list变成真正的dom)
将副作用链表映射到屏幕上后,将current树替换为workInProgress树(执行current=workInProgress)
- 判断的是当前副作用是否需要更新
- React将这个过程又分为三个步,分别是beforecommit、commit 和aftercommit。在这三个步分别调用commitBeforeMutationEffects, commitMutationEffects和commitLayoutEffects三个函数来执行具体的工作
- commit副作用之前会先在commitBeforeMutationEffects函数中处理相应的生命周期函数
- 正式commit副作用前,React会调用结点的getSnapshotBeforeUpdate生命周期函数
- 在commitMutationEffects函数中正式commit副作用,将副作用更新到屏幕(会根据EffectTag的类型执行对应的操作:插入,删除,更新等)
- commitPlacement 函数中的 node.stateNode 指向的就是当前结点对应的 DOM 元素实例。执行完 appendChildToContainer(parent, stateNode) 后应用程序中的副作用就会渲染到屏幕上
- workInProgress树赋值给current树
- commitLayoutEffects函数在更新内容commit到屏幕之后执行,主要负责commit阶段的一些收尾工作(对于ClassComponent 类型的结点,会调用该结点的生命周期函数,如componentDidMount和componentDidUpdate)
如何构建workInProgress树
React完善workInProgress对象的过程就是将元素转换为Fiber结点的过程
-
循环解析工作单元(执行workLoop 函数,称为工作循环)
在工作循环的过程中,React总是先从外部根节点开始解析(按照child路线从上向下层层分析的),当解析到叶子结点时就要开始完成工作单元。完成工作单元时如果有兄弟结点时则需要继续解析兄弟结点,然后完成兄弟结点后会逐层向上完成工作单元(检查当前结点是否有兄弟结点,如果没有兄弟结点就检查父结点有没有兄弟结点) -
解析工作单元的逻辑
- 匹配fiber结点的tag值调用对应的函数分别解析HostRoot(根组件元素),ClassComponent(组件元素),HostComponent(对应的真实DOM为div或span)和HostText等类型的fiber结点
- 为结点标记effectTag属性,标识结点的更新类型(如插入、修改或者删除)
-
完成工作单元(主要工作是收集副作用,同时处理HostComponent类型的结点,创建对应的DOM元素并将他们append到父结点上)
- 创建DOM元素或更新DOM元素中的属性(分别是首次渲染和更新渲染。应用程序首次渲染时React为HostComponent和HostText类型的Fiber结点创建对应的DOM实例并将其赋值到结点上的stateNode属性上,而在应用程序更新渲染时React不需要为它们重新创建DOM实例,而是把新的值更新到DOM元素中)
- 收集副作用,将副作用列表中的所有DOM实例(存在fiber结点的stateNode属性中)更新到屏幕中
任务调度器怎么更新任务
React中的任务都是由任务调度器scheduler统一管理(是独立于react和react-dom的模块)
任务调度器为其分配的任务执行优先级
setState() —> 为任务创建过期时间 --> 根据过期时间创建任务类型 --> 向任务调度申请执行 --> 根据过期时间将任务加入任务队列 --> 执行任务(使用requestAnimationFrame在浏览器下一次绘制之前执行函数,即用postMessage去通知执行更新任务队列,和执行任务)
var update = {
// 过期时间与任务优先级相关联
expirationTime: expirationTime,
suspenseConfig: suspenseConfig,
// tag用于标识更新的类型如UpdateState,ReplaceState,ForceUpdate等
tag: UpdateState,
// 更新内容
payload: null,
// 更新完成后的回调
callback: null,
// 下一个更新(任务)
next: null,
// 下一个副作用
nextEffect: null
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
// React给任务定义了数据结构,使其具有过期时间、下一个任务和上一个任务等属性,多个任务对象链接成一个双向循环链表
// 源码位置:packages/scheduler/src/Scheduler.js
var task = {
callback, // 任务的回调函数,主要用于和其他框架的链接,比如React fiber
priorityLevel, // 任务优先级,数值越小优先级别越高
startTime, // 任务开始执行时间
expirationTime, // 任务过期时间
next: null, // 下一个任务
previous: null // 上一个任务
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Q:过期时间依据什么来计算?
通过任务优先级计算更新的过期时间。
React 在计算当前 Fiber 结点更新对象的过期时间时,会向其任务调度器查询该更新对象应该什么样的优先级。任务调度器会根据当前计算机资源(如 CPU)的使用情况返回合适的优先级
// computeExpirationForFiber函数的作用是为Fiber结点计算过期时间,根据当前任务优先级为Fiber结点匹配对应的过期时间
// 源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.js
function computeExpirationForFiber(currentTime, fiber, suspenseConfig) {
var mode = fiber.mode; // 如果处于非批量更新模式直接按照同步任务执行
if ((mode & BatchedMode) === NoMode) { return Sync; }
// ...
var priorityLevel = getCurrentPriorityLevel() // 可获取当前更新任务的优先级
// 根据调度器的任务优先级计算过期时间
switch (priorityLevel) { // ImmediatePriority对应的数值为99
case ImmediatePriority: expirationTime = Sync; // 同步执行 break; // UserBlockingPriority对应的数值为98
case UserBlockingPriority: // 计算交互事件的过期时间 expirationTime = computeInteractiveExpiration(currentTime); break; // NormalPriority对应的数值为97,为初始值
case NormalPriority: // LowPriority对应的数值为96
case LowPriority: // 计算异步更新的过期时间 expirationTime = computeAsyncExpiration(currentTime); break; // IdlePriority对应的数值为95
case IdlePriority: expirationTime = Never; break; default: throw ReactError(Error('Expected a valid priority level'));
}
// ...
}
/* 任务调度器监听到浏览器当前的任务执行状态,
如果有交互事件,则将当前任务优先级(currentPriorityLevel)设置为UserBlockingPriority
也就是说接下来 Fiber 结点是要根据用户 交互行为阻塞情况选择适当时机执行更新的。
如果任务调度器返回的是 ImmediatePriority,一般情况是由于 Fiber 结点的更新任务一直得不到执行,
任务调度器将其改变为饥饿任务并为其提高优先执行权
*/
// computeInteractiveExpiration函数:计算交互事件的过期时间
// 下面是React定义的一些常量,单位均为ms
var HIGH_PRIORITY_EXPIRATION = 500;
var HIGH_PRIORITY_BATCH_SIZE = 100;
var LOW_PRIORITY_EXPIRATION = 5000;
var LOW_PRIORITY_BATCH_SIZE = 250;
var MAX_SIGNED_31_BIT_INT = 1073741823;
var Sync = MAX_SIGNED_31_BIT_INT;
var Batched = Sync - 1;
var MAGIC_NUMBER_OFFSET = Batched - 1;
var UNIT_SIZE = 10;
function ceiling(num, precision) {
// | 0 为取整操作
return ((num / precision | 0) + 1) * precision;
}// 计算过期时间bucket
function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
return MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE); }
// 计算交互事件过期时间的入口函数
function computeInteractiveExpiration(currentTime) {
return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE);
}
- 1
- 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
// currentTime 一般是通过window.performance.now()获取,然后转化成React中的时间
function computeInteractiveExpiration(currentTime) { return computeExpirationBucket(currentTime, 500, 100);
}
function computeExpirationBucket(currentTime, 500, 100) { // return 1073741821 - ceiling(1073741821 - currentTime + 500 / 10, 100 / 10); // return 1073741821 - ceiling(1073741821 - currentTime + 50, 10); return 1073741821 - ((1073741871 - currentTime) / 10 | 0) * 10 + 10 // 如果currentTime的值为 1073738202 过期时间为 1073738171 // 如果currentTime的值为 1073738211 过期时间依然为 1073738171 // 如果currentTime的值为 1073738212 过期时间依然为 1073738181
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
// 源码位置:packages/scheduler/src/Scheduler.js
// 优先级-立即执行
// 32位系统在V8中的最大整数大小 Math.pow(2, 30) - 1 = 0b111111111111111111111111111111
var maxSigned31BitInt = 1073741823;
var IMMEDIATE_PRIORITY_TIMEOUT = -1; // 立即过期/饥饿的任务
var USER_BLOCKING_PRIORITY = 250; // 用户阻塞任务的过期时间250ms
var NORMAL_PRIORITY_TIMEOUT = 5000; // 正常任务的过期时间5000ms
var LOW_PRIORITY_TIMEOUT = 10000; // 低优先级任务的过期时间10000ms
var IDLE_PRIORITY = maxSigned31BitInt; // 最低优先级任务的过期时间(大约12天后过期)
任务调度器会通过 performance.now() + timeout 来计算出任务的过期时间,随着时间的推移,当前时间会越来越接近这个过期时间,所以过期时间越小的代表优先级越高。如果过期时间已经比当前时间小了,说明这个任务已经过 期了还没执行,需要立马去执行
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
参考:《剖析React内部运行机制》
文章来源: blog.csdn.net,作者:sayid760,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq_14993375/article/details/115024741