React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3

  • 前言

React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript

本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java

  • 正文

上一篇講解了平臺無關的代碼,這篇繼續來說針對與 HTML DOM 操做的代碼。react

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode()                       (平臺無關)
        |-ReactReconciler.mountComponent()                 |
          |-ReactCompositeComponent.mountComponent()       |
          |-ReactCompositeComponent.performInitialMount()  |
            |-instantiateReactComponent()                 _|_
            |-ReactDOMComponent.mountComponent()       lower half
        |-_mountImageIntoNode()                      (HTML DOM 相關)
                                                          _|_

先來看看 ReactDOMComponent.mountComponent 作了什麼:算法

// 文件位置:src/renderers/dom/shared/ReactDomComponent.js

tComponent: function (
    transaction,
    hostParent,
    hostContainerInfo,
    context
) {
    ...

    var mountImage;
    
    var ownerDocument = hostContainerInfo._ownerDocument;
    var el;
    
    // document.createElement 建立 h1 元素
    el = ownerDocument.createElement(this._currentElement
                .type, props.is);

    ...
  
    // 建立 component 與 DOM 的雙向連接
    // ReactDOMComponent[ins]._hostNode 指向 DOM
    // DOM 的 __reactInternalInstance 指向 component
    ReactDOMComponentTree.precacheNode(this, el);
    
    // 設置屬性
    this._updateDOMProperties(null, props, transaction);
    
    var lazyTree = DOMLazyTree(el);
    
    // 循環建立子元素
    this._createInitialChildren(transaction, props, context,
        lazyTree);
        
    mountImage = lazyTree;

    ...

    return mountImage;
},

到此爲止,實例間的關係是這樣的:segmentfault

clipboard.png

DOM 元素建立完成後,剩下的就是將其掛載到 container 上面去了。這裏調用的是 ReactMount 的 _mountImageIntoNode:服務器

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode(                        (平臺無關)
          wrapperInstance, // scr: -> not of interest now  |
          container,   // scr: --> document.getElementById(‘root’)
          transaction, // scr: --> not of interest         |
          shouldReuseMarkup, // scr: -------> null         |
          context,           // scr: -------> not of interest
        )                                                  |
        |-ReactReconciler.mountComponent()                 |
          |-ReactCompositeComponent.mountComponent()       |
          |-ReactCompositeComponent.performInitialMount()  |
            |-instantiateReactComponent()                 _|_
            |-ReactDOMComponent.mountComponent()           |
       /* we are here */                               lower half
        |-_mountImageIntoNode()                      (HTML DOM 相關)
            markup,    // scr: --> DOMLazyTree[ins]        |
            container, // scr: --> document.getElementById(‘root’)
            wrapperInstance, // scr:----> same             |
            shouldReuseMarkup, // scr:--> same             |
            transaction, // scr: -------> same             |
          )

具體實現:app

_mountImageIntoNode: function (
        markup,        // DOMLazyTree[ins] 
        container,     // document.getElementById(‘root’)
        instance,
        shouldReuseMarkup,
        transaction
    ) {
        ...
        
        while (container.lastChild) {
            container.removeChild(container.lastChild);
        }
        
        DOMLazyTree.insertTreeBefore(container, markup, null);

        ...
    }

DOMLazyTree.insertTreeBefore 最終會調用 parentNode.insertBefore,將元素掛載到 container 上。到此爲止,首次渲染就完成啦!dom

  • 總結

從 React 啓動到元素渲染到頁面,並不像看起來這麼簡單,中間經歷了複雜的層級調用。原文的這張圖總結得很是好:學習

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

相關文章
相關標籤/搜索