React 16.8.0 初次渲染 源码分析(一)

ReactDOM.render(<List />,document.getElementById('container'));

以这个demo作为例子,ReactDOM.render 作为入口

  • packages/react-dom/src/ReactDOM.js
render(
    element: React$Element<any>, //需要渲染的组件List
    container: DOMContainer, // document.getElementById('container')
    callback: ?Function, // undefined
  ) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  }
render

render 跳转到 legacyRenderSubtreeIntoContainer

function legacyRenderSubtreeIntoContainer(
    parentComponent: ?React$Component<any, any>, // null
    children: ReactNodeList, //需要渲染的组件List
    container: DOMContainer, // document.getElementById('container')
    forceHydrate: boolean, // false
    callback: ?Function, // undefined
  ) {
    // TODO: Without `any` type, Flow says "Property cannot be accessed on any
    // member of intersection type." Whyyyyyy.
    let root: Root = (container._reactRootContainer: any);
    if (!root) {
      // Initial mount 初始化容器
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
        container,
        forceHydrate,
      );
legacyRenderSubtreeIntoContainer-initial-mount

legacyRenderSubtreeIntoContainer 先暂停到初始化容器处,看下 legacyCreateRootFromDOMContainer 如何创建 root

function legacyCreateRootFromDOMContainer(
    container: DOMContainer, // document.getElementById('container')
    forceHydrate: boolean, //false
  ): Root {
    const shouldHydrate =
      forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    // 首次初始化清空容器
    if (!shouldHydrate) {
      let warned = false;
      let rootSibling;
      while ((rootSibling = container.lastChild)) {
        container.removeChild(rootSibling);
      }
    }
    // Legacy roots are not async by default.
    const isConcurrent = false;
    return new ReactRoot(container, isConcurrent, shouldHydrate);
  }
legacyCreateRootFromDOMContainer
首次初始化清空 document.getElementById('container') 容器,后 return new ReactRoot
function ReactRoot(
    container: DOMContainer,  // document.getElementById('container')
    isConcurrent: boolean, //false
    hydrate: boolean, //false
  ) {
    const root = createContainer(container, isConcurrent, hydrate);
    this._internalRoot = root;
  }
ReactRoot
  • packages/react-reconciler/src/ReactFiberReconciler.js
export function createContainer(
    containerInfo: Container,  // document.getElementById('container')
    isConcurrent: boolean, //false
    hydrate: boolean, //false
  ): OpaqueRoot {
    return createFiberRoot(containerInfo, isConcurrent, hydrate);
  }
createContainer
  • packages/react-reconciler/src/ReactFiberRoot.js  
export function createFiberRoot(
    containerInfo: any, // document.getElementById('container')
    isConcurrent: boolean, //false
    hydrate: boolean, //false
  ): FiberRoot {
    // Cyclic construction. This cheats the type system right now because
    // stateNode is any.
    const uninitializedFiber = createHostRootFiber(isConcurrent);
createFiberRoot
  • packages/react-reconciler/src/ReactFiber.js 
export function createHostRootFiber(isConcurrent: boolean): Fiber {
  // isConcurrent = false
  let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;

  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;
  }
  // mode = NoContext
  return createFiber(HostRoot, null, null, mode);
}
createHostRootFiber-1
const createFiber = function(
  tag: WorkTag, // HostRoot = 3
  pendingProps: mixed, // null
  key: null | string, // null
  mode: TypeOfMode, // NoContext = 0
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};
createFiber
function FiberNode(
    tag: WorkTag, // HostRoot = 3
    pendingProps: mixed, // null
    key: null | string, // null
    mode: TypeOfMode, // NoContext = 0
  ) {
    // Instance
    this.tag = tag; // HostRoot = 3
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.stateNode = null;
  
    // Fiber
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;
  
    this.ref = null;
  
    this.pendingProps = pendingProps;
    this.memoizedProps = null;
    this.updateQueue = null;
    this.memoizedState = null;
    this.contextDependencies = null;
  
    this.mode = mode; // NoContext = 0
  
    // Effects
    this.effectTag = NoEffect; // 0
    this.nextEffect = null;
  
    this.firstEffect = null;
    this.lastEffect = null;
  
    this.expirationTime = NoWork; // 0
    this.childExpirationTime = NoWork; // 0
  
    this.alternate = null;
  
    if (enableProfilerTimer) {
      // Note: The following is done to avoid a v8 performance cliff.
      //
      // Initializing the fields below to smis and later updating them with
      // double values will cause Fibers to end up having separate shapes.
      // This behavior/bug has something to do with Object.preventExtension().
      // Fortunately this only impacts DEV builds.
      // Unfortunately it makes React unusably slow for some applications.
      // To work around this, initialize the fields below with doubles.
      //
      // Learn more about this here:
      // https://github.com/facebook/react/issues/14365
      // https://bugs.chromium.org/p/v8/issues/detail?id=8538
      this.actualDuration = Number.NaN;
      this.actualStartTime = Number.NaN;
      this.selfBaseDuration = Number.NaN;
      this.treeBaseDuration = Number.NaN;
  
      // It's okay to replace the initial doubles with smis after initialization.
      // This won't trigger the performance cliff mentioned above,
      // and it simplifies other profiler code (including DevTools).
      this.actualDuration = 0;
      this.actualStartTime = -1;
      this.selfBaseDuration = 0;
      this.treeBaseDuration = 0;
    }
  }
FiberNode

上接createFiberRoot-1

  let root;
  if (enableSchedulerTracing) {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      pingCache: null,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,

      interactionThreadID: unstable_getThreadID(),
      memoizedInteractions: new Set(),
      pendingInteractionMap: new Map(),
    }: FiberRoot);
  } else {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,

      pingCache: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }

  uninitializedFiber.stateNode = root;

  // The reason for the way the Flow types are structured in this file,
  // Is to avoid needing :any casts everywhere interaction tracing fields are used.
  // Unfortunately that requires an :any cast for non-interaction tracing capable builds.
  // $FlowFixMe Remove this :any cast and replace it with something better.
  return ((root: any): FiberRoot);
}    
createFiberRoot-2
上接legacyCreateRootFromDOMContainer ,暂时跳过 unbatchedUpdates 方法,直接看 return getPublicRootInstance(root._internalRoot);
      if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
          const instance = getPublicRootInstance(root._internalRoot);
          originalCallback.call(instance);
        };
      }
      // Initial mount should not be batched.
      unbatchedUpdates(() => {
        if (parentComponent != null) {
          root.legacy_renderSubtreeIntoContainer(
            parentComponent,
            children,
            callback,
          );
        } else {
          root.render(children, callback);
        }
      });
    } else {
      if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
          const instance = getPublicRootInstance(root._internalRoot);
          originalCallback.call(instance);
        };
      }
      // Update
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        root.render(children, callback);
      }
    }
    return getPublicRootInstance(root._internalRoot);
  }
legacyRenderSubtreeIntoContainer-2
root._internalRoot 就是 createFiberRoot 创建的 FiberRoot 对象
  • packages/react-reconciler/srcReactFiberReconciler.js
export function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}
getPublicRootInstance
container.current 就是 createHostRootFiber 创建的 FiberNode 对象
containerFiber.child 是 List 渲染后的FiberNode 对象,所以最终 return containerFiber.child.stateNode
 

小结

 

 

 

 

  1.  legacyCreateRootFromDOMContainer函数 unbatchedUpdates 是初次渲染主要逻辑触发点。
  2. React在初次渲染时 在 container Dom 下注入了一个属性_reactRootContainer 的 ReactRoot 对象,作为React实例的容器
  3. ReactRoot 对象下注入了一个属性 _internalRoot 属性的 FiberRoot 对象,作为Fiber的根节点

 

posted @ 2021-05-10 15:51  远方的少年🐬  阅读(99)  评论(0编辑  收藏  举报