美文网首页
React源码学习(四):渲染入口:ReactDOM.rende

React源码学习(四):渲染入口:ReactDOM.rende

作者: 青叶小小 | 来源:发表于2021-03-21 22:37 被阅读0次

    一、源码学习

    1. ReactDOM.render 对外暴露代码位于源文件:

    // react-dom/src/client/ReactDOM.js
    import {
      ......
      render,
      ......
    } from './ReactDOMLegacy';
    
    export {
       ......
      findDOMNode,
      hydrate,
      render,
      unmountComponentAtNode,
      ......
    };
    

    2. 查看最原始的ReactDOM.reader方法,发现其支持3个参数:

    • element(来自React.createElement创建的VDOM)
    • container(H5根节点)
    • callback(可选)
    // react-dom/src/client/ReactDOMLegacy.js
    export function render(
      element: React$Element<any>,
      container: Container,
      callback: ?Function,
    ) {
      ......
      return legacyRenderSubtreeIntoContainer(
         null,
         element,
        container,
        false,
        callback,
      );
    }
    

    3. 继续查看legacyRenderSubtreeIntoContainer:

    function legacyRenderSubtreeIntoContainer(
      parentComponent: ?React$Component<any, any>,
      children: ReactNodeList,
      container: Container,
      forceHydrate: boolean,
      callback: ?Function,
    ) {
       ......
    
      let root: RootType = (container._reactRootContainer: any);
      let fiberRoot;
      if (!root) {
        // Initial mount
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
          container,
          forceHydrate,
        );
        fiberRoot = root._internalRoot;
        if (typeof callback === 'function') {
          const originalCallback = callback;
          callback = function() {
            const instance = getPublicRootInstance(fiberRoot);
            originalCallback.call(instance);
          };
        }
        // Initial mount should not be batched.
        unbatchedUpdates(() => {
          updateContainer(children, fiberRoot, parentComponent, callback);
        });
      } else {
        ......
        // Update
        updateContainer(children, fiberRoot, parentComponent, callback);
      }
      return getPublicRootInstance(fiberRoot);
    }
    

    源码中,if-else中 fiberRoot这段是IF和ELSE中是一样的,只是最后的更新方式不一样。

    该方法判断是否是首次初始化(判断root是否存在):

    • 不存在则先创建root = legacyCreateRootFromDOMContainer,然后 updateContainer;
    • 存在则直接 updateContainer;

    4. 我们来查看是如何创建root的:

    • 清空container所有内容;
    • 创建root;
    function legacyCreateRootFromDOMContainer(
      container: Container,
      forceHydrate: boolean, // 调用render时该参数为false
    ): RootType {
      // shouldHydrate = false
      const shouldHydrate =
        forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    
        // 清空container所有内容
      if (!shouldHydrate) {
        let warned = false;
        let rootSibling;
        while ((rootSibling = container.lastChild)) {
          container.removeChild(rootSibling);
        }
      }
    
      return createLegacyRoot(
        container,
        shouldHydrate ? {hydrate: true}: undefined
      );
    }
    
    // react-dom/src/client/ReactDOMRoot.js
    export function createLegacyRoot(
      container: Container,
      options?: RootOptions,
    ): RootType {
      // LegacyRoot = 0
      return new ReactDOMBlockingRoot(container, LegacyRoot, options);
    }
    
    function ReactDOMBlockingRoot(
      container: Container,
      tag: RootTag,
      options: void | RootOptions,
    ) {
      this._internalRoot = createRootImpl(container, tag, options);
    }
    
    function createRootImpl(
      container: Container,
      tag: RootTag,
      options: void | RootOptions, // undefined
    ) {
      ......
      // 重点
      const root = createContainer(container, tag, false, null);
      markContainerAsRoot(root.current, container);
      ......
      return root;
    }
    

    方法 createRootImpl 中的两个重点调用:

    • createContainer;
    • markContainerAsRoot;(之后会在【legacyRenderSubtreeIntoContainer】中判断root是否已经初始化过)

    5. 继续查看如何创建 root 的:

    // react-reconciler/src/ReactFiberReconciler.js
    export function createContainer(
      containerInfo: Container,
      tag: RootTag,
      hydrate: boolean,
      hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): OpaqueRoot {
      return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
    }
    

    6. createFiberRoot干了4件事:

    创建root,类型是 FiberRootNode;

    • 创建 Fiber;
    • 初始化更新队列;
    • 返回创建好的 root ;
    // react-reconciler/src/ReactFiberRoot.js
    export function createFiberRoot(
      containerInfo: any,
      tag: RootTag,
      hydrate: boolean,
      hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): FiberRoot {
      const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
    
      // Cyclic construction. This cheats the type system right now because
      // stateNode is any.
      const uninitializedFiber = createHostRootFiber(tag);
      root.current = uninitializedFiber;
      uninitializedFiber.stateNode = root;
    
      initializeUpdateQueue(uninitializedFiber);
    
      return root;
    }
    

    FiberRootNode结构:

    // react-reconciler/src/ReactFiberRoot.js
    function FiberRootNode(containerInfo, tag, hydrate) {
      this.tag = tag;
      this.current = null;
      this.containerInfo = containerInfo;
      this.pendingChildren = null;
      this.context = null;
      this.hydrate = hydrate; // false
      ......
    }
    

    createHostRootFiber -> createFiber -> 返回 FiberNode结构:

    // react-reconciler/src/ReactFiber.js
    export function createHostRootFiber(tag: RootTag): Fiber {
      ......
      // mode = NoMode
      return createFiber(HostRoot, null, null, mode);
    }
    
    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
      return new FiberNode(tag, pendingProps, key, mode);
    };
    
    function FiberNode(
      tag: WorkTag,
      pendingProps: mixed,
      key: null | string,
      mode: TypeOfMode,
    ) {
      // Instance
      this.tag = tag;
      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.dependencies_old = null;
    
      this.mode = mode;
      ......
    }
    

    initializeUpdateQueue:

    // react-reconciler/src/ReactUpdateQueue.js
    export function initializeUpdateQueue<State>(fiber: Fiber): void {
      const queue: UpdateQueue<State> = {
        baseState: fiber.memoizedState,
        firstBaseUpdate: null,
        lastBaseUpdate: null,
        shared: {
          pending: null,
        },
        effects: null,
      };
      fiber.updateQueue = queue;
    }
    

    创建好 root 后,返回到【3. legacyRenderSubtreeIntoContainer】,调用 updateContainer 进行渲染。

    root 是个对象:

    root._internalRoot = FiberRootNode;                       // legacyRenderSubtreeIntoContainer中 this._internalRoot 赋值
    root._internalRoot.current = FiberNode;                   // createFiberRoot
    root._internalRoot.current.stateNode = root._internalRoot;// createFiberRoot
    

    二、回顾一下 render 的整体调用链

    render.png

    三、总结

    ReactDOM.render源码就先学习到这,至于 updateContainer ,涉及到 Fiber 及其 Scheduler 机制,之后会继续学习。Fiber 也是 React v16 推出的一个重点,提升了 React 的渲染性能。

    相关文章

      网友评论

          本文标题:React源码学习(四):渲染入口:ReactDOM.rende

          本文链接:https://www.haomeiwen.com/subject/okqacltx.html