React源码阅读-fiber的强相关总结

464 阅读9分钟

Fiber手写的准备工作

因为fiber是核心, react 体系的渲染和更新都要以 fiber 作为数据模型, 如果不能理解 fiber, 也无法深入理解react。但事实上如果说想了解fiber是个很残酷的事情,因为这意味着你要理解react-reconciler(协调器)、同时也要知道一些react-dom(渲染器)和scheduler(调度中心)的事情。

所以想了一下我们需要先做一些抽离把一些跟fiber强相关的东西先拿出来说一下。(这篇是属于总结篇,就是阅读到哪部分都拿到这里)。

1.fiber对象

// 一个Fiber对象代表一个即将渲染或者已经渲染的组件(ReactElement), 一个组件可能对应两个fiber(current和WorkInProgress)
export type Fiber = {|
  tag: WorkTag,
  key: null | string,
  elementType: any,
  type: any,
  stateNode: any,
  return: Fiber | null,
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,
  ref:
    | null
    | (((handle: mixed) => void) & { _stringRef: ?string, ... })
    | RefObject,
  pendingProps: any, // 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动
  memoizedProps: any, // 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中
  updateQueue: mixed, // 存储state更新的队列, 当前节点的state改动之后, 都会创建一个update对象添加到这个队列中.
  memoizedState: any, // 用于输出的state, 最终渲染所使用的state
  dependencies: Dependencies | null, // 该fiber节点所依赖的(contexts, events)等
  mode: TypeOfMode, // 二进制位Bitfield,继承至父节点,影响本fiber节点及其子树中所有节点. 与react应用的运行模式有关(有ConcurrentMode, BlockingMode, NoMode等选项).

  // Effect 副作用相关
  flags: Flags, // 标志位
  subtreeFlags: Flags, //替代16.x版本中的 firstEffect, nextEffect. 当设置了 enableNewReconciler=true才会启用
  deletions: Array<Fiber> | null, // 存储将要被删除的子节点. 当设置了 enableNewReconciler=true才会启用

  nextEffect: Fiber | null, // 单向链表, 指向下一个有副作用的fiber节点
  firstEffect: Fiber | null, // 指向副作用链表中的第一个fiber节点
  lastEffect: Fiber | null, // 指向副作用链表中的最后一个fiber节点

  // 优先级相关
  lanes: Lanes, // 本fiber节点的优先级
  childLanes: Lanes, // 子节点的优先级
  alternate: Fiber | null, // 指向内存中的另一个fiber, 每个被更新过fiber节点在内存中都是成对出现(current和workInProgress)

  // 性能统计相关(开启enableProfilerTimer后才会统计)
  // react-dev-tool会根据这些时间统计来评估性能
  actualDuration?: number, // 本次更新过程, 本节点以及子树所消耗的总时间
  actualStartTime?: number, // 标记本fiber节点开始构建的时间
  selfBaseDuration?: number, // 用于最近一次生成本fiber节点所消耗的时间
  treeBaseDuration?: number, // 生成子树所消耗的时间的总和
|};

属性解释:

  • fiber.tag: 表示 fiber 类型, 根据ReactElement组件的 type 进行生成, 在 react 内部共定义了25 种 tag.
  • fiber.key: 和ReactElement组件的 key 一致.
  • fiber.elementType: 一般来讲和ReactElement组件的 type 一致
  • fiber.type: 一般来讲和fiber.elementType一致. 一些特殊情形下, 比如在开发环境下为了兼容热更新(HotReloading), 会对function, class, ForwardRef类型的ReactElement做一定的处理, 这种情况会区别于fiber.elementType, 具体赋值关系可以查看源文件.
  • fiber.stateNode: 与fiber关联的局部状态节点(比如: HostComponent类型指向与fiber节点对应的 dom 节点; 根节点fiber.stateNode指向的是FiberRoot; class 类型节点其stateNode指向的是 class 实例).
  • fiber.return: 指向父节点.
  • fiber.child: 指向第一个子节点.
  • fiber.sibling: 指向下一个兄弟节点.
  • fiber.index: fiber 在兄弟节点中的索引, 如果是单节点默认为 0.
  • fiber.ref: 指向在ReactElement组件上设置的 ref(string类型的ref除外, 这种类型的ref已经不推荐使用, reconciler阶段会将string类型的ref转换成一个function类型).
  • fiber.pendingProps: 输入属性, 从ReactElement对象传入的 props. 用于和fiber.memoizedProps比较可以得出属性是否变动.
  • fiber.memoizedProps: 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做pendingProps, 生成子节点之后会把pendingProps赋值给memoizedProps用于下一次比较.pendingPropsmemoizedProps比较可以得出属性是否变动.
  • fiber.updateQueue: 存储update更新对象的队列, 每一次发起更新, 都需要在该队列上创建一个update对象.
  • fiber.memoizedState: 上一次生成子节点之后保持在内存中的局部状态.
  • fiber.dependencies: 该 fiber 节点所依赖的(contexts, events)等, 在context机制章节详细说明.
  • fiber.mode: 二进制位 Bitfield,继承至父节点,影响本 fiber 节点及其子树中所有节点. 与 react 应用的运行模式有关(有 ConcurrentMode, BlockingMode, NoMode 等选项).
  • fiber.flags: 标志位, 副作用标记(在 16.x 版本中叫做effectTag, 相应pr), 在ReactFiberFlags.js中定义了所有的标志位. reconciler阶段会将所有拥有flags标记的节点添加到副作用链表中, 等待 commit 阶段的处理.
  • fiber.subtreeFlags: 替代 16.x 版本中的 firstEffect, nextEffect. 默认未开启, 当设置了enableNewReconciler=true 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, 使用示例见源码.
  • fiber.deletions: 存储将要被删除的子节点. 默认未开启, 当设置了enableNewReconciler=true 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, 使用示例见源码.
  • fiber.nextEffect: 单向链表, 指向下一个有副作用的 fiber 节点.
  • fiber.firstEffect: 指向副作用链表中的第一个 fiber 节点.
  • fiber.lastEffect: 指向副作用链表中的最后一个 fiber 节点.
  • fiber.lanes: 本 fiber 节点所属的优先级, 创建 fiber 的时候设置.
  • fiber.childLanes: 子节点所属的优先级.
  • fiber.alternate: 指向内存中的另一个 fiber, 每个被更新过 fiber 节点在内存中都是成对出现(current 和 workInProgress)

2. Update 与 UpdateQueue 对象

fiber对象中有一个属性fiber.updateQueue, 是一个链式队列(即使用链表实现的队列存储结构), 后文会根据场景表述成链表或队列.

首先观察Update对象的数据结构、

export type Update<State> = {|
  eventTime: number, // 发起update事件的时间(17.0.2中作为临时字段, 即将移出)
  lane: Lane, // update所属的优先级

  tag: 0 | 1 | 2 | 3, //
  payload: any, // 载荷, 根据场景可以设置成一个回调函数或者对象
  callback: (() => mixed) | null, // 回调函数

  next: Update<State> | null, // 指向链表中的下一个, 由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象
|};

// =============== UpdateQueue ==============
type SharedQueue<State> = {|
  pending: Update<State> | null,
|};

export type UpdateQueue<State> = {|
  baseState: State,
  firstBaseUpdate: Update<State> | null,
  lastBaseUpdate: Update<State> | null,
  shared: SharedQueue<State>,
  effects: Array<Update<State>> | null,
|};

属性解释:

  1. UpdateQueue

    • baseState: 表示此队列的基础 state
    • firstBaseUpdate: 指向基础队列的队首
    • lastBaseUpdate: 指向基础队列的队尾
    • shared: 共享队列
    • effects: 用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.
  2. SharedQueue

    • pending: 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.
  3. Update

    • eventTime: 发起update事件的时间(17.0.2 中作为临时字段, 即将移出)
    • lane: update所属的优先级
    • tag: 表示update种类, 共 4 种. UpdateState,ReplaceState,ForceUpdate,CaptureUpdate
    • payload: 载荷, update对象真正需要更新的数据, 可以设置成一个回调函数或者对象.
    • callback: 回调函数. commit完成之后会调用.
    • next: 指向链表中的下一个, 由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象.

3.fiber双缓存树

什么是双缓存,就是在内存中直接进行构建然后进行替换的技术。

对应到React中,会存在两颗fiberDom树,一颗内存中fiber树,一颗页面上的fiber树,它们总是交替循坏,当更新后,页面上的fiber树->内存中的fiber树,内存中的fiber树 -> 页面上的fiber树。

// The root we're working on
let workInProgressRoot: FiberRoot | null = null;
// The fiber we're working on
let workInProgress: Fiber | null = null;

4.fiber中的构建全局变量

// 执行状态
let executionContext: ExecutionContext = NoContext;
// 页面上的fiber
let workInProgressRoot: FiberRoot | null = null;
// 内存中的fiber
let workInProgress: Fiber | null = null;
// 渲染的车道
let workInProgressRootRenderLanes: Lanes = NoLanes;
// 跟节点的状态是否是已完成,挂起等
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;

5.fiber中hooks的强相关

hooks中任意的api都是为了直接或者间接控制fiber中的状态

export type Fiber = {|
  // 1. fiber节点自身状态相关
  pendingProps: any,
  memoizedProps: any,
  updateQueue: mixed,
  memoizedState: any,

  // 2. fiber节点副作用(Effect)相关
  flags: Flags,
  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,
|};

hooks数据结构、queue环形链表,只处理hooks中的状态。

type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) => S) | null, //最新提交函数
  eagerState: S | null, //最新状态
  next: Update<S, A>,
  priority?: ReactPriorityLevel,
|};

type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null,
  dispatch: (A => mixed) | null,
  lastRenderedReducer: ((S, A) => S) | null, //队尾的提交函数指向到最新的状态
  lastRenderedState: S | null, //最后一次得到的 state
|};

export type Hook = {|
  memoizedState: any, // 当前状态
  baseState: any, // 基础状态
  baseQueue: Update<any, any> | null, // 基队列
  queue: UpdateQueue<any, any> | null, // 更新队列
  next: Hook | null, // next指针
|};

effect.tag标识符

export const NoFlags = /*  */ 0b000;
export const HasEffect = /* */ 0b001; // 有副作用, 可以被触发
export const Layout = /*    */ 0b010; // 浏览器渲染结束后执行
export const Passive = /*   */ 0b100; // dom更新完成后执行

effect对象

export type Effect = {|
  tag: HookFlags, //effct的标识符
  create: () => (() => void) | void, //effect的回调函数
  destroy: (() => void) | void, //回调函数中的return,执行销毁,清除副作用函数
  deps: Array<mixed> | null, //依赖项
  next: Effect, //指向下一个effct
|};

hooks api分类

export type HookType =
  | 'useState'
  | 'useReducer'
  | 'useContext'
  | 'useRef'
  | 'useEffect'
  | 'useLayoutEffect'
  | 'useCallback'
  | 'useMemo'
  | 'useImperativeHandle'
  | 'useDebugValue'
  | 'useDeferredValue'
  | 'useTransition'
  | 'useMutableSource'
  | 'useOpaqueIdentifier';

hooks初次构建和更新的不同的使用函数

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,
  unstable_isNewReconciler: enableNewReconciler,
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,
  unstable_isNewReconciler: enableNewReconciler,
};

hooks 全局变量

// 渲染优先级
let renderLanes: Lanes = NoLanes;
// 当前正在构造的fiber, 等同于 workInProgress
let currentlyRenderingFiber: Fiber = (null: any);
// Hooks被存储在fiber.memoizedState 链表上
let currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState
let workInProgressHook: Hook | null = null; // workInProgressHook = fiber(workInProgress).memoizedState
// 在function的执行过程中, 是否再次发起了更新. 只有function被完全执行之后才会重置.
// 当render异常时, 通过该变量可以决定是否清除render过程中的更新.
let didScheduleRenderPhaseUpdate: boolean = false;
// 在本次function的执行过程中, 是否再次发起了更新. 每一次调用function都会被重置
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
// 在本次function的执行过程中, 重新发起更新的最大次数
const RE_RENDER_LIMIT = 25;
// dev模式下,暂不分析
let currentHookNameInDev: ?HookType = null;
let hookTypesDev: Array<HookType> | null = null;
let hookTypesUpdateIndexDev: number = -1;
let ignorePreviousDependencies: boolean = false;

接下来我们分析一下我们的手写思路

  1. 我们需要通过reactElement树去构建一个简单的fiber树。
  2. 创建任务队列和实现调度逻辑,创建任务队列对标源码的reconciler(构造器或者说协调器)注册调度任务task。调度逻辑对标源码的scheduler的调度实现,去控制任务的回调时间,然后去实现任务分片以及可中断的特性。
  3. 实现简单初始渲染。
  4. 实现简单更新渲染。

总结

方便我们后面简单实现fiber的时候,做出分析。