美文网首页
React源码解析之ReactDOM.render()

React源码解析之ReactDOM.render()

作者: 小进进不将就 | 来源:发表于2019-08-14 09:05 被阅读0次

    一、React更新的方式有三种:
    (1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
    (2)setState
    (3)forceUpdate

    接下来,我们就来看下ReactDOM.render()源码

    二、ReactDOM.render(element, container[, callback])

    作用:
    在提供的container里渲染一个React元素,并返回对该组件的引用

    常见的用法是这个:

    ReactDOM.render(<App />, document.getElementById('root'));
    

    官网网址:
    https://zh-hans.reactjs.org/docs/react-dom.html#render

    源码:

    const ReactDOM: Object = {
      //服务端使用hydrate方法渲染节点
      hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
        invariant(
          isValidContainer(container),
          'Target container is not a DOM element.',
        );
    
        // TODO: throw or warn if we couldn't hydrate?
        return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          //true是让服务端尽可能复用节点,提高性能
          true,
          callback,
        );
      },
    
      render(
        //元素
        element: React$Element<any>,
        //容器
        container: DOMContainer,
        //应用渲染结束后,调用的函数
        callback: ?Function,
      ) {
        //错误抓取
        invariant(
          isValidContainer(container),
          'Target container is not a DOM element.',
        );
    
        //render方法本质是返回了函数legacyRenderSubtreeIntoContainer
        return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          //render不会复用节点,因为是前端渲染
          false,
          callback,
        );
      },
    
    }
    

    解析:
    (1)render()方法本质是返回了函数legacyRenderSubtreeIntoContainer()

    (2)hydrate()render()的唯一区别是传入legacyRenderSubtreeIntoContainer()的第四个参数不一样:
    hydrate()true,表示在服务端尽可能复用节点,提高性能;
    render()false,表示在浏览器端不会去复用节点,而是全部替换掉。

    三、legacyRenderSubtreeIntoContainer()

    作用:
    初始化Container

    源码:

    // null, element, container, false, callback,
    function legacyRenderSubtreeIntoContainer(
      parentComponent: ?React$Component<any, any>,
      children: ReactNodeList,
      container: DOMContainer,
      forceHydrate: boolean,
      callback: ?Function,
    ) {
      // TODO: Without `any` type, Flow says "Property cannot be accessed on any
      // member of intersection type." Whyyyyyy.
    
      //render中一般渲染的是DOM标签,所以不会有_reactRootContainer存在,
      // 所以第一次渲染,root是不存在的
      let root: _ReactSyncRoot = (container._reactRootContainer: any);
      let fiberRoot;
      if (!root) {
        // Initial mount
        //创建一个ReactRooter
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
          container,
          forceHydrate,
        );
        fiberRoot = root._internalRoot;
    
        //判断是否有callback
        if (typeof callback === 'function') {
          const originalCallback = callback;
          callback = function() {
            //根据fiberRoot获取公共Root实例
            //就是fiberRoot.current.child.stateNode
            const instance = getPublicRootInstance(fiberRoot);
            //通过该实例instance 去调用originalCallback方法
            originalCallback.call(instance);
          };
        }
        // Initial mount should not be batched.
        //初始化安装不应该批量更新
        unbatchedUpdates(() => {
          //element,fiberRoot,null,callback
          updateContainer(children, fiberRoot, parentComponent, callback);
        });
      } else {
        fiberRoot = root._internalRoot;
        if (typeof callback === 'function') {
          const originalCallback = callback;
          callback = function() {
            const instance = getPublicRootInstance(fiberRoot);
            originalCallback.call(instance);
          };
        }
        // Update
        updateContainer(children, fiberRoot, parentComponent, callback);
      }
      return getPublicRootInstance(fiberRoot);
    }
    

    解析:
    (1)由于是第一次渲染更新,所以rootnull,只需看!root的情况

    (2)legacyCreateRootFromDOMContainer(container,false,)的作用是创建ReactRooter,稍后会讲解

    (3)unbatchedUpdates(fn)的简化源码如下:

    unbatchedUpdates(fn){
      return fn()
    }
    

    (4)updateContainer()的作用是更新container,稍后讲解

    四、legacyCreateRootFromDOMContainer(container,forceHydrate,)

    作用:
    创建一个ReactRooter

    源码:

    //创建ReactRooter
    function legacyCreateRootFromDOMContainer(
      container: DOMContainer,
      forceHydrate: boolean,
    ): _ReactSyncRoot {
      //是否是服务端渲染
      const shouldHydrate =
        //render的forceHydrate是false,所以会调用shouldHydrateDueToLegacyHeuristic方法来判断是否是服务端渲染
        forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
      // First clear any existing content.
      //如果不是服务端渲染的话
      if (!shouldHydrate) {
        let warned = false;
        let rootSibling;
        //循环删除container的子节点
        //为什么要删除?因为React认为这些节点是不需要复用的
        while ((rootSibling = container.lastChild)) {
     
          container.removeChild(rootSibling);
        }
      }
    
      // Legacy roots are not batched.
      //container是空的container,0,false
      //ReactRoot是同步的
      //sync 同步
      //async 异步
      return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
    }
    

    解析:
    (1)render()forceHydratefalse,所以看shouldHydrateDueToLegacyHeuristic(container)是否返回false

    shouldHydrateDueToLegacyHeuristic()

    作用:
    判断是否是服务端渲染

    源码:

    //判断是否是服务端渲染
    function shouldHydrateDueToLegacyHeuristic(container) {
      //获取container的第一个节点(根节点)
      //也就是 id='root' 的节点
      const rootElement = getReactRootElementInContainer(container);
      return !!(
        rootElement &&
        rootElement.nodeType === ELEMENT_NODE &&
        //判断是否是服务端渲染
        rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
      );
    }
    

    getReactRootElementInContainer()

    作用:
    获取container中的第一个节点(或文档节点)

    源码:

    //获取Container里的RectRoot元素
    //返回document节点或第一个子节点
    function getReactRootElementInContainer(container: any) {
      if (!container) {
        return null;
      }
      //DOCUMENT_NODE 即 window.document
      if (container.nodeType === DOCUMENT_NODE) {
        return container.documentElement;
      } else {
        return container.firstChild;
      }
    }
    

    也就是说,判断是否是服务端渲染的标志是:
    在获取container中的第一个节点(或文档节点)后,看该节点是否有属性ROOT_ATTRIBUTE_NAME

    ROOT_ATTRIBUTE_NAME是什么呢?

    //服务端渲染的话,会在React App的第一个元素上添加该属性
    //以标识是服务端渲染的
    export const ROOT_ATTRIBUTE_NAME = 'data-reactroot';
    

    data-reactroot

    (2)由(1)可知,render()container的首节点是没有data-reactroot属性的,所以会进行while循环,依次删除container的子节点,删除完毕后,new 一个ReactSyncRoot()的实例

    (3)ReactSyncRoot()

    作用:
    创建ReactRoot实例

    源码:

    // container,0,false
    function ReactSyncRoot(
      container: DOMContainer,
      tag: RootTag,
      hydrate: boolean,
    ) {
      // Tag is either LegacyRoot or Concurrent Root
      const root = createContainer(container, tag, hydrate);
      this._internalRoot = root;
    }
    

    把创建的root作为legacyCreateRootFromDOMContainer()__internalRoot属性

    createContainer

    作用:
    创建React容器

    源码:

    //创建React容器
    export function createContainer(
      containerInfo: Container,
      tag: RootTag,
      hydrate: boolean,
    ): OpaqueRoot {
      //创建FiberRoot
      return createFiberRoot(containerInfo, tag, hydrate);
    }
    

    也就是说legacyCreateRootFromDOMContainer()的本质是创建了FilberRoot

    五、updateContainer()

    作用:
    创建更新container

    源码:

    //更新Container
    export function updateContainer(
      element: ReactNodeList,
      container: OpaqueRoot,
      parentComponent: ?React$Component<any, any>,
      callback: ?Function,
    ): ExpirationTime {
      const current = container.current;
      //1073741823
      const currentTime = requestCurrentTime();
    
      const suspenseConfig = requestCurrentSuspenseConfig();
      //计算过期时间,这是React优先级更新非常重要的点
      const expirationTime = computeExpirationForFiber(
        currentTime,
        current,
        suspenseConfig,
      );
      return updateContainerAtExpirationTime(
        element,
        container,
        parentComponent,
        expirationTime,
        suspenseConfig,
        callback,
      );
    }
    

    解析:

    (1)requestCurrentTime()

    作用:
    计算新开始的时间

    源码不用看,只需要知道该时间,是以V8引擎上最大31位整数1073741823为根据的:

    // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
    // Math.pow(2, 30) - 1
    // 0b111111111111111111111111111111
    //整型最大数值,是V8中针对32位系统所设置的最大值
    export default 1073741823;
    

    (2)requestCurrentSuspenseConfig()computeExpirationForFiber()以后会讲解

    (3)updateContainerAtExpirationTime()

    作用:
    每到过期时间,就更新container,过期时间单位为 10ms

    源码:

    //在过期时间内,更新container
    export function updateContainerAtExpirationTime(
      element: ReactNodeList,
      container: OpaqueRoot,
      parentComponent: ?React$Component<any, any>,
      expirationTime: ExpirationTime,
      suspenseConfig: null | SuspenseConfig,
      callback: ?Function,
    ) {
      // TODO: If this is a nested container, this won't be the root.
      const current = container.current;
    
      //由于parentComponent为null,所以返回空对象{}
      const context = getContextForSubtree(parentComponent);
      if (container.context === null) {
        container.context = context;
      } else {
        container.pendingContext = context;
      }
      //计划更新Root
      return scheduleRootUpdate(
        current,
        element,
        expirationTime,
        suspenseConfig,
        callback,
      );
    }
    

    解析:

    scheduleRootUpdate()

    作用:
    计划更新Root

    源码:

    //计划更新Root
    function scheduleRootUpdate(
      current: Fiber,
      element: ReactNodeList,
      expirationTime: ExpirationTime,
      suspenseConfig: null | SuspenseConfig,
      callback: ?Function,
    ) {
    
      //创建更新的时间节点
      const update = createUpdate(expirationTime, suspenseConfig);
      // Caution: React DevTools currently depends on this property
      // being called "element".
      update.payload = {element};
    
      callback = callback === undefined ? null : callback;
      if (callback !== null) {
        warningWithoutStack(
          typeof callback === 'function',
          'render(...): Expected the last optional `callback` argument to be a ' +
            'function. Instead received: %s.',
          callback,
        );
        update.callback = callback;
      }
    
      if (revertPassiveEffectsChange) {
        flushPassiveEffects();
      }
      //一整个React应用中,会有多次更新,而这多次更新均在更新队列中
      enqueueUpdate(current, update);
      //进行任务调度
      //当React进行Update后,就要进行调度
      //即 根据任务的优先级去调度任务
      //先执行优先级高的任务,
      scheduleWork(current, expirationTime);
    
      return expirationTime;
    }
    

    解析:
    任务调度是React中最重要、复杂的内容,之后会慢慢来解析。
    这里可以看到,React将初始化的Update放入了更新队列中,并进行任务调度,最终返回了一个expirationTime

    也就是说,updateContainer()本质是返回了expirationTime

    六、getPublicRootInstance()

    作用:
    获取root实例

    源码:

    //获取root实例
    export function getPublicRootInstance(
      container: OpaqueRoot,
    ): React$Component<any, any> | PublicInstance | null {
      //看到container.current,我就想到了ref(xxx.current)
      //获取当前节点
      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;
      }
    }
    

    解析:
    由于是 React 初始化,所以container.current是没有子节点的,所以该方法返回 null

    七、ReactDOM.render()流程图

    总结:
    ReactDOM.render() 的更新步骤
    (1)创建 ReactRoot,ReactRoot 是创建整个React应用的根对象

    (2)创建 FiberRoot 和 RootFiber

    (3)创建更新 (创建更新后,就会进入调度阶段,调度阶段由调度器进行管理)

    GitHub:
    https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/client/ReactDOM.js


    (完)

    相关文章

      网友评论

          本文标题:React源码解析之ReactDOM.render()

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