React原始碼解析(2):元件的掛載

fiveoneLei發表於2019-03-02

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

<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我暫時沒弄的太明白,但是它主要作用是優化元件的更新,所以不影響我們理解元件的掛載.好了,下面進入正題.

看看下面的程式碼如何執行的

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方法
它主要分為二個部分

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

第一部分instantiateReactComponent

instantiateReactComponent依據不同的型別產生不同的例項,每個例項都具有mountComponent方法核心方法.

型別 對應方法
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就掛載完成了.

具體的實現過程

在前面·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
);
複製程式碼

具體來看看第二個方法

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的生命週期

相關文章