ReactDOM.render做了什么-- 第三部分 初始挂载

590 阅读4分钟

我们这里跳过第二部分,先来看一下第三部分。 第二部分是在 根元素 container 内已经有组件存在的情况的处理逻辑,

第三部分则是初始情况下,container内是什么也没有的情况的处理逻辑。

先看看第三部分都有什么

_renderSubtreeIntoContainer 1

    _renderSubtreeIntoContainer:function(parentComponent, nextElement, container, callback){
        '
            看一下, getReactRootElementInContainer方法在下边单独罗列这个方法的作用是。
            
            这个方法是根据 container 的类型来返回对应的DOM。
            如果 container 类型是document,就返回整个 document文档。
            如果 container 类型是普通的元素 比如div什么的,就返回他的第一个子元素。
            初始情况下,reactRootElement 应该是个空。
        '
        
        var reactRootElement = getReactRootElementInContainer(container);
        
        '
            containerHasReactMarkup 在初始挂载的时候同样为 false 
        '
        
        var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement);
        
        '
         当然初始的情况下   containerHasNonRootReactChild 的值也是false。
         
        hasNonRootReactChild方法 简单的来说就是判断 当前的 node 也就是 container 是否有根子组件。
        '
        var containerHasNonRootReactChild = hasNonRootReactChild(container);
        
        '
        表示是否应该重新标记一下 初始的时候 也是 false
        ' 
        var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild;
        
        
        '
            这一部分的内容,请往下看
        '
        
        var component = ReactMount._renderNewRootComponent(
          nextWrappedElement, container, 
          shouldReuseMarkup, 
          parentComponent != null 
          ? parentComponent._reactInternalInstance.
          _processChildContext(parentComponent._reactInternalInstance._context) 
          : emptyObject)._renderedComponent.getPublicInstance();
          
        if (callback) {
          callback.call(component);
        }
        
        return component;
        
    }

我们先来看看这一部分用到的方法。

getReactRootElementInContainer 方法

    '
        这个方法是根据 container 的类型来返回对应的DOM。
        如果 container 类型是document,就返回整个 document文档。
        如果 container 类型是普通的元素 比如div什么的,就返回他的第一个子元素。
    '
    function getReactRootElementInContainer(container) {
          if (!container) {
            return null;
          }
          // DOC_NODE_TYPE = 9;
          // Document.nodeType==9
          if (container.nodeType === DOC_NODE_TYPE) {
            // document.documentElement则是整个HTML文件
            return container.documentElement;
          } else {
            return container.firstChild;
          }
    }

_renderSubtreeIntoContainer 2

这一部分是这一部分的核心,我们单独的摘出来说:

        parentComponent 是 null.
        这里调用 _renderNewRootComponent 方法返回一个 component。
        这里的component 是调用了_renderedComponent.getPublicInstance()之后的。
        
        getPublicInstance 是 返回组件的公开表示形式。
        比如下边的图:

这里依照惯例,调用了另外的一个方法 _renderNewRootComponent

    _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
        
         var component = ReactMount._renderNewRootComponent(
          nextWrappedElement, container, 
          shouldReuseMarkup, 
          parentComponent != null 
          ? parentComponent._reactInternalInstance.
          _processChildContext(parentComponent._reactInternalInstance._context) 
          : emptyObject)._renderedComponent.getPublicInstance();
          
        if (callback) {
          callback.call(component);
        }
        
        return component;
    }

所以来看看 _renderNewRootComponent 方法

_renderNewRootComponent 方法在第一部分已经有过说明,这里再说一次吧。

_renderNewRootComponent

这里 instantiateReactComponent 方法的使用 参考文章Element 实例化部分

    _renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
        
        '
            instantiateReactComponent 类会根据 nextElement 的类型来选择不同的类来将其实例化为一个 挂载实例。
        '
        var componentInstance = instantiateReactComponent(nextElement, null);
        
        '
            而后将 实例化的 componentInstance 通过 _registerComponent 方法 放入到 instancesByReactRootID 中。
        '
        var reactRootID = ReactMount._registerComponent(componentInstance, container);
        
        '
            采用批处理来进行处理。
        '
        ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, reactRootID, container, shouldReuseMarkup, context);
        
        '
            返回一个 componentInstance
        '
        return componentInstance;
        
    }

ReactUpdates.batchedUpdates 方法我们之前说过参考文章。简单的来说就是 使用批处理执行该方法中的第一个参数(是个函数),该方法中的后边几个参数作为第一个参数执行时候的参数。

再简单的来说就是 以 componentInstance, reactRootID, container, shouldReuseMarkup, context几个为参数调用方法 batchedMountComponentIntoNode。

所以我们还是来看看 batchedMountComponentIntoNode 方法

batchedMountComponentIntoNode

这里用到了调和事务,关于事务请参考这篇文章

首先 取出来一个 ReactReconcileTransaction 也叫做调和事务 的实例出来使用。这里使用了事务池化技术

    function batchedMountComponentIntoNode(componentInstance, rootID, container, shouldReuseMarkup, context) {
       
        var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(shouldReuseMarkup);
        
        '
            使用 调和事务来包装 mountComponentIntoNode 方法。
        '
        transaction.perform(mountComponentIntoNode, null, componentInstance, rootID, container, transaction, shouldReuseMarkup, context);
        
        '
            调和事务使用完之后放回 ''中。
        '
        ReactUpdates.ReactReconcileTransaction.release(transaction);
    }
    

batchedMountComponentIntoNode 方法的核心是使用事务 调用了 mountComponentIntoNode方法,下边我们来看看 mountComponentIntoNode 方法。

看一下 mountComponentIntoNode 方法

    function mountComponentIntoNode(componentInstance, rootID, container, transaction, shouldReuseMarkup, context) {
        
        '
            这里 会调用 ReactReconciler.mountComponent 方法生成一个 markup。markup就是一串html字符串。
            比如:
            
            <div data-reactid=".0">
                <span data-reactid=".0.0">this is app </span>
                <span data-reactid=".0.1">13</span>
                <div data-reactid=".0.2">Hello </div>
            </div>
        '
        var markup = ReactReconciler.mountComponent(componentInstance, rootID, transaction, context); -----(1)
        '
        将 componentInstance 赋值给 componentInstance._renderedComponent._topLevelWrapper 
        '
        
        componentInstance._renderedComponent._topLevelWrapper = componentInstance;
        
        '
        这里调用 _mountImageIntoNode 方法将 markup 也就是当前的全部 html挂载到 container中。
        '
        ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup, transaction);
    }

ReactReconciler.mountComponent

这里该方法会根据 internalInstance 也就是 componentInstance(挂载实例) 来执行其 mountComponent 方法。这里可以参考文章Element实例化

方法最终返回一个 markup。 而关于,不同类型的 internalInstance 比如React自定义组件生成的 internalInstance 或者是 div 生成的 internalInstance 等,有不同的 mountComponent方法。,请参考文章生成markup

ReactReconciler.mountComponent 方法如下

    mountComponent: function (internalInstance, rootID, transaction, context) {
        '
            调用挂载实例的 mountComponent 方法生成 markup
        '
        var markup = internalInstance.mountComponent(rootID, transaction, context);
        if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
            transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
        }
        return markup;
    },

_mountImageIntoNode 方法

    '
        核心是调用了 setInnerHTML 方法,将markup放置在 container内。
    '
    if (transaction.useCreateElement) {
      while (container.lastChild) {
        container.removeChild(container.lastChild);
      }
      container.appendChild(markup);
    } else {
      setInnerHTML(container, markup);
    }

setInnerHTML 方法具体做了什么请看下边的。

setInnerHTML 方法

    '
        直接将 makeup 以 innerHTML 的形式注入
    '
    var setInnerHTML = function (node, html) {
        node.innerHTML = html;
    };

总结

到此将下边这行代码做的事情做完了。

    ReactDOM.render(<App />, document.getElementById("app"));

组件被插入了 container 也就是id为app的div内。