【译】了解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
树。
这是一个高级解释:
-
当为非叶子节点调用
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
网友评论