【译】了解React源代码-初始渲染(类组件)5
【译】了解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}的一个小众案例中,它被用作文本子对象,并且仅触发了一个分支。 此分支以及整个方法将在本文中详细讨论。
_createInitialChildrenis 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_updateDOMPropertiesin {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
ReactDOMComponentcreation 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树。
这是一个高级解释:
-
当为非叶子节点调用
ReactDOMComponent.mountComponent()的外部递归时,将使用分支{2}来触发每个组件子节点的{OG}; -
当对包含文本的叶节点调用
ReactDOMComponent.mountComponent()的外部递归时,分支{1}将起作用,这将设置node.textContent;。 -
当为不包含文本的叶节点调用
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参数的type为array),它会为数组中的每个ReactElement进行调用; 当它被连续调用时(children为ReactElement),它调用内部依赖于InstantiateReactComponent(){第一篇}的上述回调,以将ReactElement转换为一个空的且未初始化的ReactDOMComonent。
Note that “inner recursion” works on DIRECT children only while the “outer recursion” traverse the ENTIRE ReactElement tree.
请注意,“内部递归”仅在“外部递归”遍历ENTIREReactElement树时适用于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