Cosen95 / blog

关注行业前沿,分享所见所学。持续输出优质文章 :rocket:
212 stars 15 forks source link

FiberRoot和RootFiber #53

Open Cosen95 opened 4 years ago

Cosen95 commented 4 years ago

FiberRootRootFiber

接下来上面的继续分析legacyCreateRootFromDOMContainer方法中的剩余内容,在函数体的结尾返回了调用createLegacyRoot方法返回的一个ReactSyncRoot实例。来看createLegacyRoot方法的定义(packages/react-dom/src/client/ReactDOMRoot.js):

// packages/react-dom/src/client/ReactDOMRoot.js
export function createLegacyRoot(
  container: Container,
  options?: RootOptions
): RootType {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

内部又实例化了ReactDOMBlockingRoot

// packages/react-dom/src/client/ReactDOMRoot.js
/**
 * ReactSyncRoot构造函数
 *
 * @param {Container} container DOM容器
 * @param {RootTag} tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param {(void | RootOptions)} options 配置信息,只有在hydrate时才有值,否则为undefined
 */
function ReactDOMBlockingRoot(
  container: Container,
  tag: RootTag,
  options: void | RootOptions
) {
  this._internalRoot = createRootImpl(container, tag, options);
}

内部调用了createRootImpl方法,并将返回结果赋值给_internalRoot,来看createRootImpl方法的定义:

/**
 * 创建并返回一个fiberRoot
 *
 * @param {Container} container DOM容器
 * @param {RootTag} tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param {(void | RootOptions)} options 配置信息,只有在hydrate时才有值,否则为undefined
 * @returns
 */
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions
) {
  // Tag is either LegacyRoot or Concurrent Root
  // 判断是否为hydrate模式
  const hydrate = options != null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;
  const mutableSources =
    (options != null &&
      options.hydrationOptions != null &&
      options.hydrationOptions.mutableSources) ||
    null;
  // 创建一个fiberRoot
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 给container附加一个内部属性用于指向fiberRoot的current属性对应的rootFiber节点
  markContainerAsRoot(root.current, container);
  const containerNodeType = container.nodeType;

  if (enableEagerRootListeners) {
    const rootContainerElement =
      container.nodeType === COMMENT_NODE ? container.parentNode : container;
    listenToAllSupportedEvents(rootContainerElement);
  } else {
    if (hydrate && tag !== LegacyRoot) {
      const doc =
        containerNodeType === DOCUMENT_NODE
          ? container
          : container.ownerDocument;
      // We need to cast this because Flow doesn't work
      // with the hoisted containerNodeType. If we inline
      // it, then Flow doesn't complain. We intentionally
      // hoist it to reduce code-size.
      eagerlyTrapReplayableEvents(container, ((doc: any): Document));
    } else if (
      containerNodeType !== DOCUMENT_FRAGMENT_NODE &&
      containerNodeType !== DOCUMENT_NODE
    ) {
      ensureListeningTo(container, "onMouseEnter", null);
    }
  }

  if (mutableSources) {
    for (let i = 0; i < mutableSources.length; i++) {
      const mutableSource = mutableSources[i];
      registerMutableSourceForHydration(root, mutableSource);
    }
  }

  return root;
}

从上述源码中,我们可以看到createRootImpl方法通过调用createContainer方法来创建一个fiberRoot实例,并将该实例返回并赋值到ReactSyncRoot构造函数的内部成员_internalRoot属性上。我们继续深入createContainer方法去探究一下fiberRoot完整的创建过程,该方法被抽取到与react-dom包同级的另一个相关的依赖包react-reconciler包中,然后定位到packages/react-reconciler/src/ReactFiberReconciler.new.js

/**
 * 内部调用createFiberRoot方法返回一个fiberRoot实例
 *
 * @export
 * @param {Container} containerInfo DOM容器
 * @param {RootTag} tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param {boolean} hydrate 判断是否是hydrate模式
 * @param {(null | SuspenseHydrationCallbacks)} hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted
 * @returns {OpaqueRoot}
 */
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks
): OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}

内部调用了createFiberRoot方法,定义在packages/react-reconciler/src/ReactFiberRoot.new.js:

/**
 * 创建fiberRoot和rootFiber并相互引用
 *
 * @export
 * @param {*} containerInfo DOM容器
 * @param {RootTag} tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param {boolean} hydrate 判断是否是hydrate模式
 * @param {(null | SuspenseHydrationCallbacks)} hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted
 * @returns {FiberRoot}
 */
export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks
): FiberRoot {
  // 通过FiberRootNode构造函数创建一个fiberRoot实例
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  // 通过createHostRootFiber方法创建fiber tree的根结点,即rootFiber
  // fiber节点也会像DOM树结构一样形成一个fiber tree单链表树结构
  // 每个DOM节点或者组件都会生成一个与之对应的fiber节点,在后续的调和(reconciliation)阶段起着至关重要的作用
  const uninitializedFiber = createHostRootFiber(tag);
  // 创建完rootFiber之后,会将fiberRoot的实例的current属性指向刚创建的rootFiber
  root.current = uninitializedFiber;
  // 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);
  // 最后将创建的fiberRoot实例返回
  return root;
}

一个完整的FiberRootNode实例包含了很多有用的属性,这些属性在任务调度阶段都发挥着各自的作用,可以看到完整的FiberRootNode构造函数的实现(这里只列举部分属性):

// packages/react-reconciler/src/ReactFiberRoot.new.js
/**
 *  FiberRootNode构造函数
 * @param containerInfo DOM容器
 * @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param hydrate 判断是否是hydrate模式
 * @constructor
 */
function FiberRootNode(containerInfo, tag, hydrate) {
  // 用于标记fiberRoot的类型
  this.tag = tag;
  // 和fiberRoot关联的DOM容器的相关信息
  this.containerInfo = containerInfo;
  // 指向当前激活的与之对应的rootFiber节点
  this.current = null;

  ...
  // 当前的fiberRoot是否处于hydrate模式
  this.hydrate = hydrate;
  ...
  // 每个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中
  this.callbackNode = null;
  // 当前任务的优先级
  this.callbackPriority = NoPriority;
  ...
}

在了解完了fiberRoot的属性结构之后,接下来继续探究createFiberRoot方法的后半部分内容:

// packages/react-reconciler/src/ReactFiberRoot.new.js > createFiberRoot
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
// 通过createHostRootFiber方法创建fiber tree的根结点,即rootFiber
// fiber节点也会像DOM树结构一样形成一个fiber tree单链表树结构
// 每个DOM节点或者组件都会生成一个与之对应的fiber节点,在后续的调和(reconciliation)阶段起着至关重要的作用
const uninitializedFiber = createHostRootFiber(tag);
// 创建完rootFiber之后,会将fiberRoot的实例的current属性指向刚创建的rootFiber
root.current = uninitializedFiber;
// 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用
uninitializedFiber.stateNode = root;

initializeUpdateQueue(uninitializedFiber);
// 最后将创建的fiberRoot实例返回
return root;

看下createHostRootFiber,定义在packages/react-reconciler/src/ReactFiber.new.js

/**
 * 内部调用createFiber方法创建一个FiberNode实例
 *
 * @export
 * @param {RootTag} tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @returns {Fiber}
 */
export function createHostRootFiber(tag: RootTag): Fiber {
  // 根据fiberRoot的标记类型来动态设置rootFiber的mode属性
  let mode;
  // mode定义在packages/react-reconciler/src/ReactTypeOfMode.js
  // export const NoMode = 0b00000; => 0
  // export const StrictMode = 0b00001; => 1
  // export const BlockingMode = 0b00010; => 2
  // export const ConcurrentMode = 0b00100; => 4
  // export const ProfileMode = 0b01000; => 8
  // export const DebugTracingMode = 0b10000;
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode | BlockingMode | StrictMode;
  } else if (tag === BlockingRoot) {
    mode = BlockingMode | StrictMode;
  } else {
    mode = NoMode;
  }

  if (enableProfilerTimer && isDevToolsPresent) {
    // Always collect profile timings when DevTools are present.
    // This enables DevTools to start capturing timing at any point–
    // Without some nodes in the tree having empty base times.
    mode |= ProfileMode;
  }
  // 调用createFiber方法创建并返回一个FiberNode实例,HostRoot表示fiber tree的根节点
  return createFiber(HostRoot, null, null, mode);
}

内部调用了createFiber,定义在packages/react-reconciler/src/ReactFiber.new.js

/**
 * 创建并返回一个FiberNode实例
 * @param {*} tag 用于标记fiber节点的类型
 * @param {*} pendingProps 表示待处理的props数据
 * @param {*} key 用于唯一标识一个fiber节点(特别在一些列表数据结构中,一般会要求为每个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场)
 * @param {*} mode 表示fiber节点的模式
 */
const createFiber = function (
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  // FiberNode构造函数用于创建一个FiberNode实例,即一个fiber节点
  return new FiberNode(tag, pendingProps, key, mode);
};

至此我们就成功地创建了一个fiber节点,上文中我们提到过,和DOM树结构类似,fiber节点也会形成一个与DOM树结构对应的fiber tree,并且是基于单链表的树结构,我们在上面刚创建的fiber节点可作为整个fiber tree的根节点,即RootFiber节点。

在目前阶段,我们暂时不用关心一个fiber节点所包含的所有属性,但可以稍微留意一下以下相关属性:

/**
 * FiberNode构造函数
 * @param tag 用于标记fiber节点的类型
 * @param pendingProps 表示待处理的props数据
 * @param key 用于唯一标识一个fiber节点(特别在一些列表数据结构中,一般会要求为每个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场)
 * @param mode 表示fiber节点的模式
 * @constructor
 */
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  // 用于标记fiber节点的类型
  this.tag = tag;
  // 用于唯一标识一个fiber节点
  this.key = key;
  ...
  // 对于rootFiber节点而言,stateNode属性指向对应的fiberRoot节点
  // 对于child fiber节点而言,stateNode属性指向对应的组件实例
  this.stateNode = null;

  // Fiber
  // 以下属性创建单链表树结构
  // return属性始终指向父节点
  // child属性始终指向第一个子节点
  // sibling属性始终指向第一个兄弟节点
  this.return = null;
  this.child = null;
  this.sibling = null;
  // index属性表示当前fiber节点的索引
  this.index = 0;
  ...

  // 表示待处理的props数据
  this.pendingProps = pendingProps;
  // 表示之前已经存储的props数据
  this.memoizedProps = null;
  // 表示更新队列
  // 例如在常见的setState操作中
  // 其实会先将需要更新的数据存放到这里的updateQueue队列中用于后续调度
  this.updateQueue = null;
  // 表示之前已经存储的state数据
  this.memoizedState = null;
  ...

  // 表示fiber节点的模式
  this.mode = mode;

  // 用于指向另一个fiber节点
  // 这两个fiber节点使用alternate属性相互引用,形成双缓冲
  // alternate属性指向的fiber节点在任务调度中又称为workInProgress节点
  this.alternate = null;
  ...
}

在本小节中我们主要是为了理解FiberRootRootFiber这两个容易混淆的概念以及两者之间的联系。同时在这里我们需要特别注意的是,多个fiber节点可形成基于单链表的树形结构,通过自身的returnchildsibling属性可以在多个fiber节点之间建立联系。