React源码解析系列(五) —— Fiber树

  • Post author:
  • Post category:其他


前面几篇文章已经介绍了React相关的api,接下来我们开始进入ReactDOM源码的学习。

本文和大家聊聊react中的虚拟DOM到底是什么?我们来看个例子

<App>
  <Card>
	<div>title</div>
	<div>content</div>
  </Card>
  <p>hello</p>
  <p>world</p>
</App>

上面的代码在经过ReactDOM一次渲染后,会构建出如下的一棵Fiber树。

这颗Fiber树的每个节点通过child、sibling、return来相互连接,最后构成一个虚拟DOM树。

下面让我们分别看下Fiber树中几种数据结构

现在只需要大致有个印象就好,随着后续代码的阅读,会逐渐明白每个属性的真正含义的

FiberRoot

type BaseFiberRootProperties = {|
  // Any additional information from the host associated with this root.
  // 指向真实DOM节点,
  // 如ReactDOM.render(<App />, document.getElementById('app'))中的第二个参数
  containerInfo: any,
  // Used only by persistent updates.
  // 这个在react-dom中不会用到
  pendingChildren: any,
  // The currently active root fiber. This is the mutable root of the tree.
  // 指向FiberRoot
  current: Fiber,

  /* 下面是更新相关的时间优先级 */
  // The following priority levels are used to distinguish between 1)
  // uncommitted work, 2) uncommitted work that is suspended, and 3) uncommitted
  // work that may be unsuspended. We choose not to track each individual
  // pending level, trading granularity for performance.
  //
  // The earliest and latest priority levels that are suspended from committing.
  // 最老和最新的在提交时被挂起任务的优先级
  earliestSuspendedTime: ExpirationTime,
  latestSuspendedTime: ExpirationTime,
  // The earliest and latest priority levels that are not known to be suspended.
  // 最老和最新的不确定是否会挂起的优先级,这是所有任务一开始进来的状态
  earliestPendingTime: ExpirationTime,
  latestPendingTime: ExpirationTime,
  // The latest priority level that was pinged by a resolved promise and can
  // be retried.
  // 这个是异步的Suspense组件更新时,它的children的promise被reslove时,重新尝试更新时的任务优先级
  latestPingedTime: ExpirationTime,


  // If an error is thrown, and there are no more updates in the queue, we try
  // rendering from the root one more time, synchronously, before handling
  // the error.
  // 在渲染阶段出现无法处理的错误时会被设置为true
  didError: boolean,

  // 正在等待提交任务的优先级(expirationTime过期时间)
  pendingCommitExpirationTime: ExpirationTime,
  // A finished work-in-progress HostRoot that's ready to be committed.
  // 本次更新完成后,最终需要被提交的RootFiber
  finishedWork: Fiber | null,
  // Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
  // it's superseded by a new one.
  // 在任务被挂起的时候通过setTimeout设置后返回的id,用来下一次如果有新的任务挂起时清理还没触发的timeout
  timeoutHandle: TimeoutHandle | NoTimeout,
  // Top context object, used by renderSubtreeIntoContainer
  // 顶层context对象,只有主动调用renderSubtreeIntoContainer时才会有用
  context: Object | null,
  pendingContext: Object | null,
  // Determines if we should attempt to hydrate on the initial mount
  // 用来确定第一次渲染是否是服务端渲染(ssr)
  +hydrate: boolean,
  // Remaining expiration time on this root.
  // TODO: Lift this into the renderer
  // 下一个需要被处理的任务对应的优先级(expirationTime过期时间)
  nextExpirationTimeToWorkOn: ExpirationTime,
  // 本次更新对应的优先级
  expirationTime: ExpirationTime,
  // List of top-level batches. This list indicates whether a commit should be
  // deferred. Also contains completion callbacks.
  // TODO: Lift this into the renderer
  // 批处理相关的
  firstBatch: Batch | null,
  // Linked-list of roots
  // 连接多个FiberRoot
  // react支持多次调用ReactDOM.render,每次都会生成对应的FiberRoot
  nextScheduledRoot: FiberRoot | null,
|};

Fiber

export type Fiber = {|
  // These first fields are conceptually members of an Instance. This used to
  // be split into a separate type and intersected with the other Fiber fields,
  // but until Flow fixes its intersection bugs, we've merged them into a
  // single type.

  // An Instance is shared between all versions of a component. We can easily
  // break this out into a separate object to avoid copying so much to the
  // alternate versions of the tree. We put this on a single object for now to
  // minimize the number of objects created during the initial render.

  // Tag identifying the type of fiber.
  // 用来表示当前Fiber对应的是那种类型的组件,如ClassComponent、FunctionComponent等
  tag: WorkTag,

  // Unique identifier of this child.
  // 组件的key
  key: null | string,

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  // 组件对应的ReactElemnt里的type,比如class组件对应那个class
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // 一般都等于elementType,但是通过lazy加载的组件,一开始会为null,直到加载完成才会设置为正确的type
  type: any,

  // The local state associated with this fiber.
  // 对应的实例,比如class组件对应class实例,原生的组件对应dom
  stateNode: any,

  // Conceptual aliases
  // parent : Instance -> return The parent happens to be the same as the
  // return fiber since we've merged the fiber and instance.

  // Remaining fields belong to Fiber

  // The Fiber to return to after finishing processing this one.
  // This is effectively the parent, but there can be multiple parents (two)
  // so this is only the parent of the thing we're currently processing.
  // It is conceptually the same as the return address of a stack frame.
  // 指向父节点
  return: Fiber | null,

  // Singly Linked List Tree Structure.
  // 指向子节点
  child: Fiber | null,
  // 指向兄弟节点
  sibling: Fiber | null,
  // 当前fiber在父节点对应子节点中的位置
  index: number,

  // The ref last used to attach this node.
  // I'll avoid adding an owner field for prod and model that as functions.
  // 组件的ref
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  // Input is the data coming into process this fiber. Arguments. Props.
  // 等待被更新的属性
  pendingProps: any, // This type will be more specific once we overload the tag.
  // 上一次渲染完成后的属性
  memoizedProps: any, // The props used to create the output.

  // A queue of state updates and callbacks.
  // 等待更新的任务队列
  updateQueue: UpdateQueue<any> | null,

  // The state used to create the output
  // 上一次渲染完成的state
  memoizedState: any,

  // A linked-list of contexts that this fiber depends on
  // 当前组件的第一个context,可以有多个连成链表
  firstContextDependency: ContextDependency<mixed> | null,

  // Bitfield that describes properties about the fiber and its subtree. E.g.
  // the ConcurrentMode flag indicates whether the subtree should be async-by-
  // default. When a fiber is created, it inherits the mode of its
  // parent. Additional flags can be set at creation time, but after that the
  // value should remain unchanged throughout the fiber's lifetime, particularly
  // before its child fibers are created.
  // 当前组件的mode,默认继承夫节点。比如是否处于异步渲染等
  mode: TypeOfMode,

  // Effect
  // 用来记录本次更新在当前组件产生的副作用,比如新增、修改、删除等
  effectTag: SideEffectTag,

  // Singly linked list fast path to the next fiber with side-effects.
  // 用链表记录产生的副作用
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  // 副作用链表中的第一个和最后一个
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  // Represents a time in the future by which this work should be completed.
  // Does not include work found in its subtree.
  // 本次更新任务的fiber对应的优先级
  expirationTime: ExpirationTime,

  // This is used to quickly determine if a subtree has no pending changes.
  // 它的子组件对应的更新优先级
  childExpirationTime: ExpirationTime,

  // This is a pooled version of a Fiber. Every fiber that gets updated will
  // eventually have a pair. There are cases when we can clean up pairs to save
  // memory if we need to.
  // 指向当前fiber对应的一个拷贝
  alternate: Fiber | null,

  /* 下面是调试相关的,收集每个Fiber和子树的渲染时间 */
  // Time spent rendering this Fiber and its descendants for the current update.
  // This tells us how well the tree makes use of sCU for memoization.
  // It is reset to 0 each time we render and only updated when we don't bailout.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  selfBaseDuration?: number,

  // Sum of base times for all descedents of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  treeBaseDuration?: number,

  // Conceptual aliases
  // workInProgress : Fiber ->  alternate The alternate used for reuse happens
  // to be the same as work in progress.
  // __DEV__ only
  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
|};

Update

export type Update<State> = {
  // 本次更新任务的优先级(过期时间)
  expirationTime: ExpirationTime,

  // 这次更新的类型,比如this.setState()对应0,this.enqueueForceUpdate()对应2
  // export const UpdateState = 0;
  // export const ReplaceState = 1;
  // export const ForceUpdate = 2;
  // export const CaptureUpdate = 3;
  tag: 0 | 1 | 2 | 3,
  // 本次更新的内容,比如this.setState({ title: 'hello' }),这时payload为{ title: 'hello' }
  payload: any,
  // 这次更新的回调函数,比如this.setState({}, callback)
  callback: (() => mixed) | null,
  // 因为可以在一个组件上多次调用this.setState,这时会产生多个更新update,通过next连接
  next: Update<State> | null,
  // 用于记录更新完成后需要执行的副作用,比如上面的callback,这个也是一个链表的形式
  nextEffect: Update<State> | null,
};

当用户调用一些会产生更新的方法时,会创建一个update。比如

this.setState({ title: 'hello' }, () => console.log('hello'));
this.setState(() => ({ title: 'world' }), () => console.log('world'));

const update1 = {
  expirationTime: 动态计算的一个时间值,
  tag: 0,
  payload: { title: 'hello' },
  callback: () => console.log('hello'),
  next: update2,
  nextEffect: update2
};

const update2 = {
  expirationTime: 动态计算的一个时间值,
  tag: 0,
  payload: () => ({ title: 'world' }),
  callback: () => console.log('world'),
  next: update1,
  nextEffect: update1
}

update形成的链表是一个循环链表,也就是最后一个的next会指向第一个

UpdateQueue

export type UpdateQueue<State> = {
  // 上次更新完成后的state,用于本次更新使用
  baseState: State,

  // 记录update链表中的第一个update,也就是上面提到的update.next维护的链表
  firstUpdate: Update<State> | null,
  // 记录update链表中的最后一个update
  lastUpdate: Update<State> | null,

  // 更新过程中可能会出现异常,这时候react会尝试去更新,这时会创建一个用于更新异常的update
  // 这里就是维护异常更新的链表中的第一个和最后一个,每个update通过update.next连接
  firstCapturedUpdate: Update<State> | null,
  lastCapturedUpdate: Update<State> | null,

  // 记录update产生副作用的链表中的第一个和最后一个,通过update.nextEffect连接
  firstEffect: Update<State> | null,
  lastEffect: Update<State> | null,

  // 记录异常处理产生的update的副作用链表,通过update.nextEffect连接
  firstCapturedEffect: Update<State> | null,
  lastCapturedEffect: Update<State> | null,
};

这个updateQueue对应fiber里的updateQueue,每当组件调用会产生更新的函数时,比如setState,就是产生一个update,加入到组件对应的fiber中的updateQueue中,在后续更新流程中就会遍历组件的updateQueue来计算出本次更新最终的state。



版权声明:本文为wlqdbtx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。