React Fiber(二)

2020-02-05  本文已影响0人  依然还是或者其他

前言

在看了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<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允许渲染进程分段完成,而不是必须一次性完成。主要功能如下:

Scheduler

在React 15.x版本中,组件的状态变更将直接导致其子组件树的重新渲染,新版本Fiber将在调度器方面进行全面改进,主要有:


任务优先级是fiber数据结构中的expirationTime来决定的,到期时间越短,则代表优先级越高,需要尽早执行。

所谓的到期时间(ExpirationTime),是相对于调度器初始调用的起始时间而言的一个时间段;调度器初始调用后的某一段时间内,需要调度完成这项更新,这个时间段长度值就是到期时间值。


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. 处理更新队列中的更新并返回新状态对象;

若进行异步执行任务:

  1. requestIdleCallback: 在线程空闲时期调度执行低优先级函数;
  2. requestAnimationFrame: 在下一个动画帧调度执行高优先级函数;

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

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

上一篇 下一篇

猜你喜欢

热点阅读