【譯】了解React源代碼-初始渲染(簡(jiǎn)單組件)3

【譯】了解React源代碼-初始渲染(簡(jiǎn)單組件)1
【譯】了解React源代碼-初始渲染(簡(jiǎn)單組件)2
【譯】了解React源代碼-初始渲染(簡(jiǎn)單組件)3
【譯】了解React源代碼-初始渲染(類組件)4
【譯】了解React源代碼-初始渲染(類組件)5
【譯】了解React源代碼-UI更新(事務(wù))6
【譯】了解React源代碼-UI更新(事務(wù))7
【譯】了解React源代碼-UI更新(單個(gè)DOM)8
【譯】了解React源代碼-UI更新(DOM樹)9


上一次我們完成了平臺(tái)不可知邏輯(也就是上半部分),該邏輯將ReactElement [1]嵌入到ReactCompositeComponent [T]中,然后使用它來派生ReactDOMComponent [ins]

這次,我將討論如何使用ReactDOMComponent [ins]創(chuàng)建可渲染的HTML DOM元素,并完成JSX-to-UI過程。

本文中使用的文件:

renderers / dom / shared / ReactDOMComponent.js
創(chuàng)建可渲染的h1 DOM元素

renderers / dom / client / utils / DOMLazyTree.js
將h1添加到DOM樹

renderers / dom / client / ReactMount.js
以上兩個(gè)動(dòng)作重新討論交叉點(diǎn)

ReactDOMComponent.mountComponent()-使用document.createElement()創(chuàng)建DOM元素

It calls HTML DOM APIs and hits the bottom.
它調(diào)用HTML DOM API并觸底。

數(shù)據(jù)結(jié)構(gòu):

數(shù)據(jù)結(jié)構(gòu)

調(diào)用棧:

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode()                (platform agnostic)
        |-ReactReconciler.mountComponent()                 |
          |-ReactCompositeComponent.mountComponent()       |
          |-ReactCompositeComponent.performInitialMount()  |
            |-instantiateReactComponent()                 _|_
            /* we are here*/                               |
            |-ReactDOMComponent.mountComponent(        lower half
                transaction,                    (HTML DOM specific)
                hostParent,                                |
                hostContainerInfo,                         |
                context, (same)                            |
              )                                            |

ReactDOMComponent.mountComponent()是一個(gè)相當(dāng)長(zhǎng)且復(fù)雜的函數(shù)。 因此,我對(duì)必填字段進(jìn)行了染色(下面代碼內(nèi),必填字段使用 // **標(biāo)記),以便輕松追溯其來源。 接下來,我們看看它是如何實(shí)現(xiàn)的。

mountComponent: function (
  transaction,       // scr: -----> not of interest
  hostParent,        // scr: -----> null
  hostContainerInfo, // scr: -----> ReactDOMContainerInfo[ins]
  context            // scr: -----> not of interest
) {

// scr: --------------------------------------------------------> 1)
  this._rootNodeID = globalIdCounter++;
  this._domID = hostContainerInfo._idCounter++;
  this._hostParent = hostParent;
  this._hostContainerInfo = hostContainerInfo;

  var props = this._currentElement.props;

  switch (this._tag) { // scr: ---> no condition is met here
...
  }

... // scr: -----> sanity check

// 我們?cè)谄涓溉萜鞯拿臻g中創(chuàng)建標(biāo)記,但HTML標(biāo)記不獲得命名空間。
  var namespaceURI;
  var parentTag;

  if (hostParent != null) { // scr: -----> it is null
...
  } else if (hostContainerInfo._tag) {
    namespaceURI = hostContainerInfo._namespaceURI; // scr: -------> "http://www.w3.org/1999/xhtml"
    parentTag = hostContainerInfo._tag;        // scr: ------> "div"
  }
  if (namespaceURI == null || 
      namespaceURI === DOMNamespaces.svg && 
      parentTag ===  'foreignobject' // **
  ) { // scr: -----> no
...
  }

  if (namespaceURI === DOMNamespaces.html) {
    if (this._tag === 'svg') {  // **              // scr: -----> no
...
    } else if (this._tag === 'math') { // **       // scr: -----> no
...
    }
  }

  this._namespaceURI = namespaceURI;  // scr: ---------------------> "http://www.w3.org/1999/xhtml"

... // scr: ------> DEV code

  var mountImage;

  if (transaction.useCreateElement) { // scr: ---------------------> transaction related logic, we assume it is true
    var ownerDocument = hostContainerInfo._ownerDocument;
    var el;

    if (namespaceURI === DOMNamespaces.html) {
      if (this._tag === 'script') { // **         // scr: -----> no
...
      } else if (props.is) {                // scr: -----> no
...
      } else {
        // 分隔else分支,而不使用`props.is || 未定義`,原因是Firefox錯(cuò)誤。 
        //請(qǐng)參閱https://github.com/facebook/react/pull/6896中的討論
        //和https://bugzilla.mozilla.org/show_bug.cgi?id=1276240中的討論

// scr: --------------------------------------------------------> 2)
        // scr: ---------> HTML DOM API
        el = ownerDocument.createElement(this._currentElement.type);
      }
    } else { // scr: ------> no
...
    }

// scr: --------------------------------------------------------> 3)
    ReactDOMComponentTree.precacheNode(this, el); // scr: --------> doubly link (._hostNode & .internalInstanceKey)
    this._flags |= Flags.hasCachedChildNodes; // scr: ------------>
bit wise its flags

// scr: --------------------------------------------------------> 4)
    if (!this._hostParent) { // scr: ------> it is the root element
      DOMPropertyOperations.setAttributeForRoot(el); // scr: -----> data-reactroot
    }

// scr: --------------------------------------------------------> 5)
    this._updateDOMProperties( //*6
      null,
      props,
      transaction
    ); // scr: --------------------------> style:{ “color”: “blue” }

// scr: --------------------------------------------------------> 6)
    var lazyTree = DOMLazyTree(el); // scr: ------> DOMLazyTree[ins]

// scr: --------------------------------------------------------> 7)
    this._createInitialChildren( //*7
      transaction,
      props,
      context,
      lazyTree
    ); // scr: --------------------------> textContent:‘hello world’
    
    mountImage = lazyTree;
  } else { // if (transaction.useCreateElement)
...
  }

  switch (this._tag) { // scr: ---> no condition is met here
...
  }

  return mountImage;
}

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

1)使用參數(shù)和全局變量初始化一些ReactDOMComponent [ins]的屬性。然后所有if條件都通過,直到

2)調(diào)用HTML DOM API document.createElement() 來創(chuàng)建 h1 DOM element 。

3)ReactDOMComponentTree.precacheNode()用于在ReactDOMComponent [ins]h1 DOM element 之間創(chuàng)建雙向鏈接,它將ReactDOMComponent [ins] ._ hostNode鏈接到 h1 DOM element ,并將元素的internalInstanceKey鏈接回ReactDOMComponent [ins]。

4)由于null _hostParent,表示內(nèi)部根組件,所以DOMPropertyOperations.setAttributeForRoot()element.data-reactroot設(shè)置為空字符串“”,以在外部標(biāo)記根元素。

5)_updateDOMProperties是一個(gè)復(fù)雜的方法?,F(xiàn)在,我們只需要知道該方法從ReactDOMComponent [ins] ._ currentElement.props中提取{“ color”:“ blue”}并將其附加到DOM的style屬性。在討論“ setState()觸發(fā)的UI更新”時(shí),我們將更詳細(xì)地介紹此方法。搜索

*6

在本文中,當(dāng)您需要檢查首次使用此函數(shù)時(shí)。

6)實(shí)例化DOMLazyTree [ins]。

7)_createInitialChildren是另一個(gè)復(fù)雜的方法。 現(xiàn)在,我們只需要知道該方法從ReactDOMComponent [ins] ._ currentElement.children中提取“ hello world”并將其附加到DOM的textContent即可。 在討論“復(fù)合組件渲染”時(shí),我們將更詳細(xì)地介紹這種方法。 您可以搜尋

*7

然后,將DOMLazyTree [ins]一直返回到 上一篇文章 mountComponentIntoNode()中討論的交叉點(diǎn)。

mountImageIntoNode()-將DOM裝入容器節(jié)點(diǎn)

首先,讓我復(fù)制上一篇文章的調(diào)用??蚣堋?/p>

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode(                  (platform agnostic)
          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 specific)
            markup,    // scr: --> DOMLazyTree[ins]        |
            container, // scr: --> document.getElementById(‘root’)
            wrapperInstance, // scr:----> same             |
            shouldReuseMarkup, // scr:--> same             |
            transaction, // scr: -------> same             |
          )                                               _|_

現(xiàn)在執(zhí)行:

_mountImageIntoNode: function (
  markup,
  container,
  instance,
  shouldReuseMarkup,
  transaction)
{
  if (shouldReuseMarkup) { // scr: -------> no
…
  }

  if (transaction.useCreateElement) {//scr:>again, assume it is true
    while (container.lastChild) {    // scr: -------> null
…
    }

// scr: -------------------------> the only effective line this time
    DOMLazyTree.insertTreeBefore(container, markup, null);
  } else {
…
  }

… // scr: DEV code
}

ReactMount@renderers/dom/client/ReactMount.js

盡管看似復(fù)雜,但只有一行有效,它調(diào)用insertTreeBefore。

var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function (
  parentNode,   // scr: -----> document.getElementById(‘root’)
  tree,         // scr: -----> DOMLazyTree[ins]
  referenceNode // scr: -----> null
) {
  if (tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE ||
      tree.node.nodeType === ELEMENT_NODE_TYPE && 
      tree.node.nodeName.toLowerCase() === 'object' &&   
      (tree.node.namespaceURI == null ||
       tree.node.namespaceURI === DOMNamespaces.html)) { // scr:->no
...
  } else {
    parentNode.insertBefore(tree.node, referenceNode);
    insertTreeChildren(tree); // scr: -> returned directly in Chrome
  }
});

DOMLazyTree@renderers/dom/client/utils/DOMLazyTree.js

在此函數(shù)內(nèi)部,唯一有效的一行是:

parentNode.insertBefore(tree.node, referenceNode);

這是另一個(gè) HTML DOM API ,按照J(rèn)SX開頭的指示,將 DOMLazyTree [ins] .node(即 h1 DOM element )插入#root元素:

…
ReactDOM.render(
  <h1 style={{“color”:”blue”}}>hello world</h1>,
  document.getElementById(‘root’)
);
…

到目前為止的結(jié)論

`React.createElement()`?—?create a `ReactElement`
`_renderSubtreeIntoContainer()`?—?attach `TopLevelWrapper` to the `ReactElement[1]`
`instantiateReactComponent()`?—?create a `ReactCompositeComponent` using `ReactElement[2]`
`ReactCompositeComponent.mountComponent()`?—?initialize `ReactCompositeComponent[T]`
`ReactCompositeComponent.performInitialMount()`?—?create a `ReactDOMComponent` from `ReactElement[1]`
`ReactDOMComponent.mountComponent()`?—?create the DOM element with document.createElement()
`mountImageIntoNode()`?—?mount the DOM into the container node

—尾注—

在本系列文章中,我重點(diǎn)介紹非常原始的操作-h1組件掛載,盡管簡(jiǎn)單,它仍貫穿渲染過程的完整關(guān)鍵路徑。 我認(rèn)為這次快速的端到端之旅可以幫助讀者建立初步的知識(shí)基礎(chǔ)和信心,以進(jìn)一步探索未知領(lǐng)域。 它將變得更加復(fù)雜和有趣。

盡管已盡全力模擬包括明確目標(biāo)和具體成果的真實(shí)體驗(yàn),但強(qiáng)烈建議您通過直接在Chrome中調(diào)試來嘗試完整版的源代碼。

React 16使用 fiber reconciler作為新架構(gòu)。 如果您迫不及待想檢查一下,請(qǐng)繼續(xù)。Happy hacking!

(原文鏈接)Understanding The React Source Code - Initial Rendering (Simple Component) III

(上一篇)【譯】了解React源代碼-初始渲染(簡(jiǎn)單組件)2

(下一篇)【譯】了解React源代碼-初始渲染(類組件)4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容