React Fiber(二)

前言

在看了React Fiber初探后,有点迷糊,做了如下梳理。感谢作者bfe !!

在协调中,由于使用了新的Fiber Reconciler,才能让新版调度成为可能。
而Fiber Reconciler中是以fiber这种的基础调和单元来完成的。

Part1:Fiber 基础的单元的数据结构

  1. 单个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 | 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 | 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 中有调度的逻辑,主要体现为:

  1. 通过fiber.return属性,从当前fiber实例层层遍历至组件树根组件;
  2. 依次对每一个fiber实例进行到期时间判断,若大于传入的期望任务到期时间参数,则将其更新为传入的任务到期时间;
  3. 调用requestWork方法开始处理任务,并传入获取的组件树根组件FiberRoot对象和任务到期时间expirationTime;

在requestWork中:

  1. 首先比较任务剩余到期时间和期望的任务到期时间,若大于,则更新值;
  2. 判断任务期望到期时间(expirationTime),区分同步或异步执行任务;

Scheduler二:更新队列:将调度好的任务插入队列,并进行更新(即Reconciliation)

Fiber切分任务为多个任务单元(Work Unit)后,需要划分优先级然后存储在更新队列,随后按优先级进行调度执行。

Fiber更新队列由ReactFiberUpdateQueue模块实现,主要涉及:

  1. 创建更新队列;
  2. 添加更新至更新队列;
  3. 添加更新至fiber(即fiber实例对应的更新队列);
  4. 处理更新队列中的更新并返回新状态对象;

若进行异步执行任务:

  • 低优先级任务由requestIdleCallback处理;
  • 高优先级任务,如动画相关的由requestAnimationFrame处理;
  • requestIdleCallback可以在多个空闲期调用空闲期回调,执行任务;
  • requestIdleCallback方法提供deadline,即任务执行限制时间,以切分任务,避免长时间执行,阻塞UI渲染而导致掉帧;
    PS:
  1. requestIdleCallback: 在线程空闲时期调度执行低优先级函数;
  2. requestAnimationFrame: 在下一个动画帧调度执行高优先级函数;

留有的疑问:
Fiber 如何处理的中断的任务,如何继续?
目前自我答疑:
参考别的文章及结合上面所述,任务是插入一个更新队列,有介于Fiber是链表结构,知道在哪处中断,那么就可以在那处继续开始任务。(如有错误,望指正,希望能有更好的答案)

参考:
React Fiber初探——bfe
这可能是最通俗的 React Fiber(时间分片) 打开方式——荒山
React Fiber——妖僧风月

你可能感兴趣的:(React Fiber(二))