React源代碼解析(2):組件的掛載

上一章jsx語法是如何解析的講到了node

<div>
    <div>1</div>
    <div>2</div>
    <div>3</div>
</div>
複製代碼

jsx語法是如何解析爲虛擬dom的,接下來我將聊聊虛擬dom是如何掛載到真實dom上的。 我讀的是React^15.6.2的源代碼,由於最新的React^16.6.3版本,引入了Fiber架構,由於時間有限,Fiber我暫時沒弄的太明白,可是它主要做用是優化組件的更新,因此不影響咱們理解組件的掛載.好了,下面進入正題.react

看看下面的代碼如何執行的bash

import React from "./lib/react";
import  {render}  from "./lib/react-dom";

const Child = () => <div>
    Child
</div>
class App extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            name:"leiwuyi"
        }
    }
    render(){
        return (
            <div>App</div>
        )
    }
}

render(<div className="leiwuyi">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <App ></App>
</div>, document.getElementById("root")); 

複製代碼

ReactDom.render()實際上是運行了_renderSubtreeIntoContainer 它接收了nextElement, container參數. 首選將container這個Element類型的節點包裝成Component類型,架構

var nextWrappedElement = React.createElement(
       TopLevelWrapper,
       {
          child: nextElement
        }
 );
而後執行ReactMount._renderNewRootComponent(
        nextWrappedElement,
        container,
        shouldReuseMarkup,
        nextContext
 )
複製代碼

這個方法執行完畢,其實組件就已經掛載到root節點上去了,來看看 ReactMount._renderNewRootComponent方法 它主要分爲二個部分app

// 第一部分
var componentInstance = instantiateReactCompone
nt(
    nextElement,
    false
);
// 第二部分 
ReactUpdates.batchedUpdates(
    batchedMountComponentIntoNode,
    componentInstance,
    container,
    shouldReuseMarkup,
    context
);
複製代碼

第一部分instantiateReactComponent

instantiateReactComponent依據不一樣的類型產生不一樣的實例,每一個實例都具備mountComponent方法核心方法.dom

類型 對應方法
nullundefined ReactEmptyComponent
元素類型 ReactHostComponent.createInternalComponent( element)
component的類型 ReactCompositeComponent
文本類型 `ReactHostComponent.createInstanceForText(node)

ReactHostComponent.createInternalComponent( element) 最終是運行的ReactDOMComponent.函數

第二部分 MountComponentIntoNode

就是經過事務的形式來運行mountComponentIntoNode方法 而mountComponentIntoNode方法最終是運行實例的mountComponent方法。 該方法會產生對應的真實dom節點markup; 產生以後而後經過setInnerHTML(container, markup);插入到container裏面. 簡單的講就是拿到真實dom插入到container裏面去,這段代碼執行完畢真實dom就掛載完成了.post

具體的實現過程

在前面·nextElement包裝成Component類型·,因此最終產生的實例的原型對象是ReactCompositeComponent 實例有了那麼接下來就是執行 componentInstance.mountComponent方法; 該方法執行的ReactCompositeComponent.mountComponent 接下來看看該方法到底作了什麼. 下面我就幾個核心方法進行一個說明.優化

ReactCompositeComponent.mountComponent

第一個方法 實例化組件
this._constructComponent(
     doConstruct,
     publicProps,
     publicContext,
     updateQueue
 )
產生組件的實例 如 <App > ---> new App() 就是咱們常在組件中使用的this對象
第二個方法
拿到組件對應的真實dom
markup = this.performInitialMount(
       renderedElement,
       hostParent,
       hostContainerInfo,
       transaction,
       context
);
複製代碼

具體來看看第二個方法ui

this.performInitialMount

// 調用this.render或者func() 拿到虛擬dom,
if (renderedElement === undefined) {
     renderedElement = this._renderValidatedComponent();
}
// 拿到一個element類型的componentInstance(上文中有的)
var child = this._instantiateReactComponent(
    renderedElement,
    nodeType !==
    ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
// 拿到真實dom
var markup = ReactReconciler.mountComponent(
    child,
    transaction,
    hostParent,
    hostContainerInfo,
    this._processChildContext(context),
    debugID
);
複製代碼

這裏面的ReactReconciler.mountComponent實際上是調用的ReactDOMComponet.mountComponent方法。

因此this.performInitialMount 簡單的講就是 拿到組件的虛擬dom,而後獲取它對應的componentInstance實例而後執行ReactDOMComponet.mountComponent拿到真實dom 因此咱們接下來要看看ReactDOMComponet.mountComponent方法的實現過程

ReactDOMComponet.mountComponent

函數前面一部分其實對tag的類型進行了處理 咱們直接略過把tag當作div

建立了一個div節點
 el = ownerDocument.createElement(
    this._currentElement.type
  );
// 設置該節點的屬性
this._updateDOMProperties(
    null,
    props,
    transaction
);
// 構建它孩子的真實dom而後插入到該節點中去
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(
    transaction,
    props,
    context,
    lazyTree
);
因此執行以後lazyTree就是一個完整的真實dom節點
複製代碼

咱們來看看 this._createInitialChildren方法, 核心代碼在這裏

var mountImages = this.mountChildren(
        childrenToUse,
        transaction,
        context
    );
執行的是
var children = this._reconcilerInstantiateChildren(
    nestedChildren,
    transaction,
    context
);
// 產生child對應的實例
for (var name in children) {
    if (children.hasOwnProperty(name)) {
        var child = children[name];
        var selfDebugID = 0;
        if (
            "development" !== "production"
        ) {
            selfDebugID = getDebugID(this);
        }
        var mountImage = ReactReconciler.mountComponent(
            child,
            transaction,
            this,
            this._hostContainerInfo,
            context,
            selfDebugID
        );
        child._mountIndex = index++;
        mountImages.push(mountImage);
    }
}
複製代碼

看到這裏,就已經很明顯這是一個深度優先遍歷;它的child又產生了一個實例而後執行 mountComponent方法拿到真實dom,直到拿到最後一級孩子的真實dom而後不斷向上遞歸插入父級。直到插入到最頂層這樣就拿到了Component的真實dom。最後插入到 container容器裏面.

下一章將聊聊React的生命週期

相關文章
相關標籤/搜索