前言
在看了React Fiber初探后,有点迷糊,做了如下梳理。感谢作者bfe !!
在协调中,由于使用了新的Fiber Reconciler,才能让新版调度成为可能。
而Fiber Reconciler中是以fiber这种的基础调和单元来完成的。
Part1:Fiber 基础的单元的数据结构
- 单个fiber 就是一个对象,下面是截取React16.8源码中的部分代码,主要是结合下面说的看的
(注释部分机翻,部分大佬已翻)
export type Fiber = {|
//fiber 类型
tag: WorkTag,
//唯一标识
key: null | string,
//元素标识的类型
elementType: any,
// fiber对应的function/class/module类型组件名.
type: any,
//fiber的本地状态
stateNode: any,
// 处理完当前fiber后返回的fiber,
// 返回当前fiber所在fiber树的父级fiber实例
return: Fiber | null,
// fiber树结构相关链接
child: Fiber | null,
sibling: Fiber | null,
index: number,
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
pendingProps: any, // This type will be more specific once we overload the tag.
// 缓存的之前组件props对象
memoizedProps: any, // The props used to create the output.
// 组件状态更新及对应回调函数的存储队列
updateQueue: UpdateQueue<any> | null,
// 缓存的之前组件state对象
memoizedState: any,
//这个Fiber所依赖的上下文链表
contextDependencies: ContextDependencyList | null,
//描述Fiber及其子树的属性
//ConcurrentMode表示子树是否应该是async-by-default
//当创建一个Fiber时,其将继承其父节点的mode
//可以在创建时设置其他标志,但在此之后,值应该在整个光纤的生命周期内保持 不变,特别是在创建其子Fiber之前。
mode: TypeOfMode,
//effect 副作用
effectTag: SideEffectTag,
//在链表中下一个有副作用的Fiber
nextEffect: Fiber | null,
//在链表中第一个有副作用的Fiber
firstEffect: Fiber | null,
//在链表中最后有副作用的Fiber
lastEffect: Fiber | null,
// 更新任务的最晚执行时间,即更新任务应该在某已时间段内完成
expirationTime: ExpirationTime,
//用于确定 子树是否有无待执行的更新任务
childExpirationTime: ExpirationTime,
// fiber的版本池,即记录fiber更新过程,便于恢复
alternate: Fiber | null,
//在当前更新中渲染该fiber及其子fiber 所花费的时间
//这个可以帮助我们如何更好地利用sCU进行缓存和记忆
//在我们不修复时,每次渲染和更新时 它会被重置为0
//此字段仅在启用enableProfilerTimer标志时设置
actualDuration?: number,
//
actualStartTime?: number,
selfBaseDuration?: number,
treeBaseDuration?: number,
// __DEV__ only
_debugID?: number,
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
_debugNeedsRemount?: boolean,
_debugHookTypes?: Array<HookType> | null,
|};
Part2:Reconciliation与Scheduler
Reconciliation:
简单理解其实就是diff的那一部分。
React15.x版本及以前,计算组件树变更将会阻塞整个线程,整个渲染过程是连续不中断的,而其他任务就是被阻塞,如动画等,这将会导致用户感觉卡顿等。这时的渲染还是以树形结构和递归完成,也是造成一问题的原因之一。
React16.x中采用新版的Fiber 架构,不再是树形结构,而是采用的链表结构。
在上面fiber数据结构中:
// fiber树结构相关链接
child: Fiber | null,
sibling: Fiber | null,
index: number,
就很好的体现了。
新版Fiber Reconciliation允许渲染进程分段完成,而不是必须一次性完成。主要功能如下:
- 可切分,可中断任务;
- 可重用各分阶段任务,且可以设置优先级;(fiber 数据结构也有相关体现)
- 可以在父子组件任务间前进后退切换任务;
- render方法可以返回多元素(即可以返回数组);
- 支持异常边界处理异常;
Scheduler
在React 15.x版本中,组件的状态变更将直接导致其子组件树的重新渲染,新版本Fiber将在调度器方面进行全面改进,主要有:
-
合并多次更新:没有必要在组件的每一个状态变更时都立即触发更新任务,有些中间状态变更其实是对更新任务所耗费资源的浪费,就比如用户发现错误点击时快速操作导致组件某状态从A至B再至C,这中间的B状态变更其实对于用户而言并没有意义,那么我们可以直接合并状态变更,直接从A至C只触发一次更新;
-
任务优先级:不同类型的更新有不同优先级,例如用户操作引起的交互动画可能需要有更好的体验,其优先级应该比完成数据更新高;
-
推拉式调度:基于推送的调度方式更多的需要开发者编码间接决定如何调度任务,而拉取式调度更方便React框架层直接进行全局自主调度;
任务优先级是fiber数据结构中的expirationTime来决定的,到期时间越短,则代表优先级越高,需要尽早执行。
所谓的到期时间(ExpirationTime),是相对于调度器初始调用的起始时间而言的一个时间段;调度器初始调用后的某一段时间内,需要调度完成这项更新,这个时间段长度值就是到期时间值。
- 若当前处于任务提交阶段(更新提交至DOM渲染)时,设置当前fiber到期时间为Sync,即同步执行模式;
- 若处于DOM渲染阶段时,则需要延迟此fiber任务,将fiber到期时间设置为下一次DOM渲染到期时间;
-若不在任务执行阶段,则需重新设置fiber到期时间:
1.若明确设置useSyncScheduling且fiber.internalContextTag值不等于 AsyncUpdates,则表明是同步模式,设置为Sync;
2.否则,调用computeAsyncExpiration方法重新计算此fiber的到期时间;
Scheduler一:调度任务,即根据任务优先级判断任务
在函数scheduleWork 中有调度的逻辑,主要体现为:
- 通过fiber.return属性,从当前fiber实例层层遍历至组件树根组件;
- 依次对每一个fiber实例进行到期时间判断,若大于传入的期望任务到期时间参数,则将其更新为传入的任务到期时间;
- 调用requestWork方法开始处理任务,并传入获取的组件树根组件FiberRoot对象和任务到期时间expirationTime;
在requestWork中:
- 首先比较任务剩余到期时间和期望的任务到期时间,若大于,则更新值;
- 判断任务期望到期时间(expirationTime),区分同步或异步执行任务;
Scheduler二:更新队列:将调度好的任务插入队列,并进行更新(即Reconciliation)
Fiber切分任务为多个任务单元(Work Unit)后,需要划分优先级然后存储在更新队列,随后按优先级进行调度执行。
Fiber更新队列由ReactFiberUpdateQueue模块实现,主要涉及:
- 创建更新队列;
- 添加更新至更新队列;
- 添加更新至fiber(即fiber实例对应的更新队列);
- 处理更新队列中的更新并返回新状态对象;
若进行异步执行任务:
- 低优先级任务由requestIdleCallback处理;
- 高优先级任务,如动画相关的由requestAnimationFrame处理;
- requestIdleCallback可以在多个空闲期调用空闲期回调,执行任务;
- requestIdleCallback方法提供deadline,即任务执行限制时间,以切分任务,避免长时间执行,阻塞UI渲染而导致掉帧;
PS:
- requestIdleCallback: 在线程空闲时期调度执行低优先级函数;
- requestAnimationFrame: 在下一个动画帧调度执行高优先级函数;
留有的疑问:
Fiber 如何处理的中断的任务,如何继续?
目前自我答疑:
参考别的文章及结合上面所述,任务是插入一个更新队列,有介于Fiber是链表结构,知道在哪处中断,那么就可以在那处继续开始任务。(如有错误,望指正,希望能有更好的答案)
参考:
React Fiber初探——bfe
这可能是最通俗的 React Fiber(时间分片) 打开方式——荒山
React Fiber——妖僧风月
网友评论