【译】了解React源代码-初始渲染(类组件)5

2019-10-15  本文已影响0人  nextChallenger

【译】了解React源代码-初始渲染(简单组件)1
【译】了解React源代码-初始渲染(简单组件)2
【译】了解React源代码-初始渲染(简单组件)3
【译】了解React源代码-初始渲染(类组件)4
【译】了解React源代码-初始渲染(类组件)5
【译】了解React源代码-UI更新(事务)6
【译】了解React源代码-UI更新(事务)7
【译】了解React源代码-UI更新(单个DOM)8
【译】了解React源代码-UI更新(DOM树)9


上一次,我们完成了类组件渲染逻辑的上半部分,尽管在以下几个方面与简单组件渲染有所不同,但它们相似:1)实例化一个附加的ReactCompositeComponent来表示类组件(App); 2)调用App.render(),触发级联的React.createElement()来建立ReactElement树。

这次,我们将通过检查树中的ReactElement如何转换为它们各自的ReactDOMComponent,以及最终转换为实际DOM对象,来探索下半部分的更多分支。

本文中使用的文件:

renderers / dom / shared / ReactDOMComponent.js
提供了本文重点介绍的方法之一_createInitialChildren

renderers / dom / client / utils / setTextContent.js
DOM操作,设置文本

renderers / dom / client / utils / DOMLazyTree.js
DOM操作,附加子级

renderers / shared / stack / reconciler / ReactMultiChild.js
traverseAllChildren的中间方法以及焦点中的另一个方法mountChildren()

shared / utils / traverseAllChildren.js
迭代直接子ReactElement并实例化它们各自的ReactDOMComponent的方法

调用堆栈中使用的符号: 循环

I use {} to reference the previous post that is relevant to the methods (or logic process) being discussed.
我使用{}来引用与所讨论的方法(或逻辑过程)相关的先前文章。

本文讨论的过程主要发生在ReactDOMComponent [6] .mountComponent()中。 该方法的主要任务是从ReactDOMComponent [6]派生一个DOM对象,在{第三篇}中介绍。 该任务编号为0),以供以后参考。

在本文中,我们将解决上次有意忽略的方法之一_createInitialChildren,该方法用于将新引入的ReactElement树作为类组件的子代处理。 在{第三篇 *7}的一个小众案例中,它被用作文本子对象,并且仅触发了一个分支。 此分支以及整个方法将在本文中详细讨论。

_createInitialChildren is our protagonist today; please search *7 in post three if you want to check its role in the simple component rendering. The other overlooked method _updateDOMProperties in {post three *6} will be discussed in later articles.
_createInitialChildren是我们今天的主角; 如果要检查其在简单组件渲染中的作用,请在第三篇文章中搜索 *7。 {第三篇 *6}中另一个被忽略的方法_updateDOMProperties将在以后的文章中讨论。

更具体地说,此方法1)将ReactElement转换为其相应的ReactDOMComonents; 2)(递归)调用ReactDOMComponent [*]。mountComponent()创建DOM对象; 和3)将它们附加到在0)中创建的根DOM节点。

因此,首先让我们回顾一下类组件的上下文中的步骤0)。

ReactDOMComponent [6] .mountComponent()(在_createInitialChildren之前)—创建DOM元素[6]

time-saving hint: this paragraph is here to keep the post self-contained, the detail of the ReactDOMComponent creation process has been covered in {post three}
节省时间的提示:此段是为了保持帖子自成体系,ReactDOMComponent创建过程的详细信息已在{第三篇}中进行了介绍

数据结构:

调用栈:

...
|~mountComponentIntoNode()                                    |
  |-ReactReconciler.mountComponent()                          |
    |-ReactCompositeComponent[T].mountComponent()             |
      |-ReactCompositeComponent[T].performInitialMount()  upper half
        |-ReactReconciler.mountComponent()                    |
          |-ReactCompositeComponent[ins].mountComponent()     |
            |-this.performInitialMount()                      |
              |-this._renderValidatedComponent()              |
              |-instantiateReactComponent()                  _|_ 
                (we are here)                                 |
              |-ReactDOMComponent[6].mountComponent(          |
                  transaction, // scr: -----> not of interest |
                  hostParent,  // scr: -----> null            |
                  hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins]                                lower half
                  context      // scr: -----> not of interest |
                )                                             |
...

此步骤使用ReactDOMComponent [6]创建一个DOM对象,并设置其属性。

回顾一下:1)初始化ReactDOMComponent [6]的属性; 2)使用document.createElement()创建一个div DOM元素; 3)在ReactDOMComponent [6]和DOM对象之间创建一个双向链接; 4)和5)设置新创建的DOM对象的属性和属性; 和6)将DOM对象嵌入DOMLazyTree [1]

mountComponent: function (
  transaction,
  hostParent,
  hostContainerInfo,
  context
) {

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

  var props = this._currentElement.props;

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

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

// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
  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 {
        // Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
        // See discussion in https://github.com/facebook/react/pull/6896
        // and discussion in 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]
    this._createInitialChildren(transaction, props, context, lazyTree);
...
  } // if (transaction.useCreateElement)

  return mountImage;
}

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

ReactDOMComponent[6] gets its DOM node, its children are next in the line
ReactDOMComponent [6]得到它的DOM节点,它的孩子在该行的下一个

ReactDOMComponent [6] ._ createInitialChildren()-创建DOM元素[2-5]

指定的数据结构:

如前所述,此方法用于在{第三篇}中创建字符串子节点(“ hello world”)。 渲染相似的节点(即[3]和[5])时,我们将在本文中重用该分支,并将其命名为分支{1}。

对于类组件,第一次访问此方法时,路由{2}被命中。 具体来说,此分支处理ReactElement树。 如前所述,它1)将ReactElements转换为ReactDOMComponents(a),生成具有ReactDOMComponents的DOM节点(b),以及2)在最后一步中将DOM节点插入由ReactDOMComponent [6]生成的根节点。

_createInitialChildren: function (
  transaction, // scr: not of interest
  props,       // scr: -------------------> ReactElement[6].props
  context,     // scr: not of interest
  lazyTree     // scr: -------------------> DOMLazyTree[ins]
) {
  // Intentional use of != to avoid catching zero/false.
  // scr: it is named as 'dangerous', let's avoid touching it
  var innerHTML = props.dangerouslySetInnerHTML;
  if (innerHTML != null) { // scr: so no innerHTML
...
  } else {
    var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null;

    var childrenToUse = contentToUse != null ? null : props.children;
    // scr: some comments
    if (contentToUse != null) {
      // scr: some comments
      if (contentToUse !== '') { // scr: ----------------> route {1}
...// scr: DEV code
        DOMLazyTree.queueText(lazyTree, contentToUse);
      }
    } else if (childrenToUse != null) { // scr: ---------> route {2}
      var mountImages = this.mountChildren(childrenToUse, transaction, context);  // scr: --------------------------------> 1)
      for (var i = 0; i < mountImages.length; i++) { scr: ------> 2)
        DOMLazyTree.queueChild(lazyTree, mountImages[i]);
      }
    }
  }
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

The call hierarchy and iteration is a bit complex from now on, so this time I’ll first establish an overview of the big picture before diving into any detail.
从现在开始,呼叫的层次结构和迭代有点复杂,因此这次我将首先对总体情况进行概述,然后再探讨任何细节。

调用栈:

...                                            (outer recursion)
ReactDOMComponent[6].mountComponent()    <-------------------------|
    (we are here)                                                  |
  |-this._createInitialChildren()                                  |
  ?{1}                                                             |
    |-DOMLazyTree.queueText()                                      |
  ?{2}                                                             |
    |-this.mountChildren()        // scr: ---------------> 1)(a)   |
      |-this._reconcilerInstantiateChildren()                      |
        |-ReactChildReconciler.instantiateChildren()               |
          |-traverseAllChildren()                                  |
            |-traverseAllChildrenImpl()  <------|inner             |
              |↻traverseAllChildrenImpl() ------|recursion         |
                |-instantiateChild()                               |
                  |-instantiateReactComponent()                    |
      |↻ReactDOMComponent.mountComponent()      // scr: -> 1)(b)---|
    |↻DOMLazyTree.queueChild()    // scr: ---------------> 2)
...

首先,我们检查在DOM级别上运行的(复杂)堆栈的底部(通过了解最终目的,我们可以在面对复杂调用图时有所放心)。

DOMLazyTree.queueText()和DOMLazyTree.queueChild()

在此演练中,DOMLazyTree.queueText()只有一行有效:

function queueText(tree, text) {
  if (enableLazy) { // scr: NO, I mean, false
...
  } else {
    setTextContent(tree.node, text);
  }
}

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

var setTextContent = function (node, text) {
  if (text) {
    var firstChild = node.firstChild;

  if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { // scr: false
...
    }
  }
  node.textContent = text; // scr: the only effective line
};

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

Node.textContent是一个DOM标准属性,可以很好地表示节点的文本内容。 显然,这是路线{1}的最终目的。

DOMLazyTree.queueChild()也有一行:

function queueChild(parentTree, childTree) {
  if (enableLazy) { // scr: again, false
...
  } else {
    parentTree.node.appendChild(childTree.node);
  }
}

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

这里Node.appendChild()是另一个DOM标准API,它将一个节点作为子节点插入另一个节点。 显然,这是路线{2}的最后一站。

现在,我们可以将这两种方法替换为它们各自的本质线。

...                                          (outer recursion)
ReactDOMComponent[6].mountComponent()    <-------------------------|
  |-this._createInitialChildren()                                  |
  ?{1}                                                             |
    |-node.textContent = text;                                     |
  ?{2}                                                             |
    |-this.mountChildren()        // scr: ---------------> 1)(a)   |
      |-this._reconcilerInstantiateChildren()                      |
        |-ReactChildReconciler.instantiateChildren()               |
          |-traverseAllChildren()                                  |
            |-traverseAllChildrenImpl()  <------|inner             |
              |↻traverseAllChildrenImpl() ------|recursion         |
                |-instantiateChild()                               |
                  |-instantiateReactComponent()                    |
      |↻ReactDOMComponent.mountComponent() // scr: ------> 1)(b)---|
    |↻node.appendChild()                   // scr: ------> 2)
...

推断大局

为此,我们从已知的方法开始。

首先,instantiateReactComponent()ReactElement实例化一个ReactDOMComponent(现在在树中没有任何“复合” ReactElement,因此所有创建的组件都是ReactDOMComponents),这也是深度嵌套调用层次结构的结尾{第二篇}

其次,ReactDOMComponent.mountComponent()初始化在上一步中创建的ReactDOMComponent,并基于它们创建相应的DOM节点。 {第三篇}&{开始}

考虑到以上两个操作是{OG}(operation group),现在更容易解释如何处理其余ReactElement树。

这是一个高级解释:

  1. 当为非叶子节点调用ReactDOMComponent.mountComponent()的外部递归时,将使用分支{2}来触发每个组件子节点的{OG};

  2. 当对包含文本的叶节点调用ReactDOMComponent.mountComponent()的外部递归时,分支{1}将起作用,这将设置node.textContent;。

  3. 当为不包含文本的叶节点调用ReactDOMComponent.mountComponent()的外部递归时,将根本不会调用_createInitialChildren()

请注意,在此过程中,将反复使用ReactDOMComponent.mountComponent()为相应的ReactDOMComponent实例创建DOM节点,因此,如果它不在(大脑)缓存中,则可能需要在本文开头检查其实现。

现在该使调用堆栈发挥作用了:

...
ReactDOMComponent[6].mountComponent()
  |-this._createInitialChildren()
    |-this.mountChildren() 
...           |↻instantiateReactComponent()[4,5]
      |-ReactDOMComponent[5].mountComponent()
        |-this._createInitialChildren()
          |-node.textContent = text; // scr: [5] done
      |-ReactDOMComponent[4].mountComponent()
        |-this._createInitialChildren()
          |-this.mountChildren() 
...                 |↻instantiateReactComponent()[2,3]
          |-ReactDOMComponent[2].mountComponent() // scr: [2] done
          |-ReactDOMComponent[3].mountComponent()
            |-this._createInitialChildren()
              |-node.textContent = text; // scr: [3] done
        |↻node[4].appendChild()[2,3] // scr: [4] done

    |↻node[6].appendChild()[4,5] // scr: [6] done
...

在此调用堆栈中,我省略了用于在ReactElement实例化ReactDOMComponent时使用的深层嵌套调用层次结构,接下来我们将对其进行检查。

InstantiateReactComponent()的深层嵌套循环

特别是,我们需要注意参数,以便在涉及递归和回调的相当深而复杂的链中跟踪输入和输出。

ReactDOMComponent._createInitialChildren()内部开始:

...
var mountImages = this.mountChildren(
  childrenToUse, // scr:----------> ReactElement[6].props.children
  transaction,   // scr: not of interest
  context        // scr: not of interest
);
...

接下来,我们看一下ReactDOMComponent.mountChildren()的实现。 如前所述,它1)实例化ReactDOMComponents的所有子级; 和2)通过调用ReactDOMComponent.mountComponent()初始化那些ReactDOMComponent

mountChildren: function (
  nestedChildren, // scr:----------> ReactElement[6].props.children
  transaction,    // scr: not of interest
  context         // scr: not of interest
) {
  
  // scr: ------------------------------------------------------> 1)
  var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
  this._renderedChildren = children;
  var mountImages = [];
  var index = 0;
  for (var name in children) {
    if (children.hasOwnProperty(name)) {
      var child = children[name];
      var selfDebugID = 0;
...// scr: DEV code
                                                   (outer recursion)      
      // scr: --------------------------------------------------> 2)
      var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);
      
      child._mountIndex = index++;
      mountImages.push(mountImage);
    }
  }
...// scr: DEV code
  return mountImages;
},

ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js

2)在之前被称为“外部递归”,并且是另一个ReactReconciler.mountComponent(){第二篇},所以我们关注1)

_reconcilerInstantiateChildren: function (
  nestedChildren, // scr:----------> ReactElement[6].props.children
  transaction,    // scr: not of interest
  context         // scr: not of interest
) {
...// scr: DEV code
  return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context);
},

ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js

这是直接调用

instantiateChildren: function (
  nestedChildNodes, // scr: --------> ReactElement[6].props.children
  transaction,      // scr: not of interest
  context,          // scr: not of interest
  selfDebugID
) // 0 in production and for roots {
  if (nestedChildNodes == null) {
    return null;
  }
  var childInstances = {};
  if (process.env.NODE_ENV !== 'production') {
...// scr: DEV code
  } else {
    traverseAllChildren(nestedChildNodes, instantiateChild, childInstances);
  }
  return childInstances;
},

instantiateChildren@renderers/shared/stack/reconciler/ReactChildReconciler.js

再次,这是对traverseAllChildren()的直接调用。 请注意,instantiateChild是每个子级调用的回调方法。

function instantiateChild(
  childInstances, // scr: ---> the output parameter childInstances is passed all the way down here
  child,      // scr: --> a ReactElement
  name,       // scr: --> unique name for indexing in childInstances
  selfDebugID // scr: --> undefined
) {
... // scr: DEV code
  }
  if (child != null && keyUnique) {
    childInstances[name] = instantiateReactComponent(child, true);
  }
}

instantiateChild@renderers/shared/stack/reconciler/ReactChildReconciler.js

反过来,它直接调用InstantiateReactComponent(){第一篇}。

我们继续traverseAllChildren()

function traverseAllChildren(
  children, // scr: ---------> ReactElement[6].props.children
  callback, // scr: ---------> instantiateChild
  traverseContext // scr: ---> output parameter, initialized as {}
) {
  if (children == null) {
    return 0;
  }
  return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

traverseAllChildren@shared/utils/traverseAllChildren.js

另一个直接调用traverseAllChildrenImpl()

function traverseAllChildrenImpl(
  children,  // scr: ---------> ReactElement[6].props.children
  nameSoFar, // scr: ---------> ''
  callback,  // scr: ---------> instantiateChild
  traverseContext // scr: ---> output parameter, initialized as {}
) {
  var type = typeof children;
  if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
  }
// scr: -------------------------------------------------------> {a}
  if (children === null || type === 'string' || type === 'number' || type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) {
    callback(traverseContext, children,
    // If it's the only child, treat the name as if it was wrapped in an array
    // so that it's consistent if the number of children grows.
    nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
    return 1;
  }
  var child;
  var nextName;
  var subtreeCount = 0; // Count of children found in the current subtree.
  var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// scr: -------------------------------------------------------> {b}
  if (Array.isArray(children)) {
    for (var i = 0; i < children.length; i++) {
      child = children[i];
      nextName = nextNamePrefix + getComponentKey(child, i);
      subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
    }
  } else {
... // scr: this branch will not be called here
  }
  return subtreeCount;
}

traverseAllChildrenImpl@shared/utils/traverseAllChildren.js

在直接对另一种方法进行单行调用之后,traverseAllChildrenImpl()是真正的工作场所。 此方法在不久之前也被称为“内部递归”。

traverseAllChildrenImpl()的逻辑很简单:第一次调用时(children参数的typearray),它会为数组中的每个ReactElement进行调用; 当它被连续调用时(childrenReactElement),它调用内部依赖于InstantiateReactComponent(){第一篇}的上述回调,以将ReactElement转换为一个空的且未初始化的ReactDOMComonent

Note that “inner recursion” works on DIRECT children only while the “outer recursion” traverse the ENTIRE ReactElement tree.
请注意,“内部递归”仅在“外部递归”遍历ENTIRE ReactElement树时适用于DIRECT子代。

将所有ReactElement转换为ReactDOMComonents之后,输出将一直返回到ReactDOMComponent.mountChildren()并完成循环。

To better understand the full circle, you might need to refer to different pieces of the puzzle back and forth, for example, the beginning of this text where ReactDOMComponent.mountComponent() is discussed; the two DOM operations (Node.appendChild, Node.textContent) that define the stack bottom; the discussion of the big picture; as well as this section.
为了更好地理解整个循环,您可能需要前后来回指一下难题的不同部分,例如,本文的开头讨论了ReactDOMComponent.mountComponent()。 定义栈底部的两个DOM操作( (Node.appendChild, Node.textContent)); 全局的讨论; 以及本节。

最后,在生成所有DOM节点之后,逻辑返回到ReactReconciler.mountComponent()并将新的节点树插入到指定的div容器中。 {第三篇}

...
|~mountComponentIntoNode()                                    |
  |-ReactReconciler.mountComponent()                          |
    |-ReactCompositeComponent[T].mountComponent()             |
      |-ReactCompositeComponent[T].performInitialMount()  upper half
        |-ReactReconciler.mountComponent()                    |
          |-ReactCompositeComponent[ins].mountComponent()     |
            |-this.performInitialMount()                      |
              |-this._renderValidatedComponent()              |
              |-instantiateReactComponent()                  _|_ 
              |-ReactDOMComponent[6].mountComponent(          |
                  transaction, // scr: -----> not of interest |
                  hostParent,  // scr: -----> null            |
                  hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins]                                    | 
                  context      // scr: -----> not of interest |
                )                                             |
                                                              |
... // the content of this section                        lower half
        |-_mountImageIntoNode()                  (HTML DOM specific)
            markup,    // scr: --> DOMLazyTree[ins]           |
            container, // scr: --> document.getElementById(‘root’)
            wrapperInstance, // scr:----> same                |
            shouldReuseMarkup, // scr:--> same                |
            transaction, // scr: -------> same                |
          )                                                  _|_

(原文地址)Understanding The React Source Code - Initial Rendering (Class Component) V

(上一篇)【译】了解React源代码-初始渲染(类组件)4

(下一篇)【译】了解React源代码-UI更新(事务)6

上一篇 下一篇

猜你喜欢

热点阅读