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用于下一次比较.pendingProps和memoizedProps比较可以得出属性是否变动.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,
|};
属性解释:
-
UpdateQueuebaseState: 表示此队列的基础 statefirstBaseUpdate: 指向基础队列的队首lastBaseUpdate: 指向基础队列的队尾shared: 共享队列effects: 用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数.
-
SharedQueuepending: 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来.
-
UpdateeventTime: 发起update事件的时间(17.0.2 中作为临时字段, 即将移出)lane:update所属的优先级tag: 表示update种类, 共 4 种.UpdateState,ReplaceState,ForceUpdate,CaptureUpdatepayload: 载荷,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;
接下来我们分析一下我们的手写思路
- 我们需要通过reactElement树去构建一个简单的fiber树。
- 创建任务队列和实现调度逻辑,创建任务队列对标源码的reconciler(构造器或者说协调器)注册调度任务task。调度逻辑对标源码的scheduler的调度实现,去控制任务的回调时间,然后去实现任务分片以及可中断的特性。
- 实现简单初始渲染。
- 实现简单更新渲染。
总结
方便我们后面简单实现fiber的时候,做出分析。