【译】了解React源代码-初始渲染(简单组件)1
【译】了解React源代码-初始渲染(简单组件)2
【译】了解React源代码-初始渲染(简单组件)3
【译】了解React源代码-初始渲染(类组件)4
【译】了解React源代码-初始渲染(类组件)5
【译】了解React源代码-UI更新(事务)6
【译】了解React源代码-UI更新(事务)7
【译】了解React源代码-UI更新(单个DOM)8
【译】了解React源代码-UI更新(DOM树)9
上一次我们完成了平台不可知逻辑(也就是上半部分),该逻辑将ReactElement [1]
嵌入到ReactCompositeComponent [T]
中,然后使用它来派生ReactDOMComponent [ins]
。
这次,我将讨论如何使用ReactDOMComponent [ins]
创建可渲染的HTML DOM元素,并完成JSX-to-UI过程。
本文中使用的文件:
renderers / dom / shared / ReactDOMComponent.js:
创建可渲染的h1 DOM元素
renderers / dom / client / utils / DOMLazyTree.js:
将h1添加到DOM树
renderers / dom / client / ReactMount.js:
以上两个动作重新讨论交叉点
ReactDOMComponent.mountComponent()-使用document.createElement()创建DOM元素
It calls HTML DOM APIs and hits the bottom.
它调用HTML DOM API并触底。
数据结构:

调用栈:
|=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()
是一个相当长且复杂的函数。 因此,我对必填字段进行了染色(下面代码内,必填字段使用 // **标记),以便轻松追溯其来源。 接下来,我们看看它是如何实现的。
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
// 我们在其父容器的命名空间中创建标记,但HTML标记不获得命名空间。
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错误。
//请参阅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)使用参数和全局变量初始化一些ReactDOMComponent [ins]
的属性。然后所有if条件都通过,直到
2)调用HTML DOM API document.createElement() 来创建 h1 DOM element 。
3)ReactDOMComponentTree.precacheNode()
用于在ReactDOMComponent [ins]
和 h1 DOM element 之间创建双向链接,它将ReactDOMComponent [ins] ._ hostNode
链接到 h1 DOM element ,并将元素的internalInstanceKey
链接回ReactDOMComponent [ins]
。
4)由于null
_hostParent
,表示内部根组件,所以DOMPropertyOperations.setAttributeForRoot()
将element.data-reactroot
设置为空字符串“”
,以在外部标记根元素。
5)_updateDOMProperties
是一个复杂的方法。现在,我们只需要知道该方法从ReactDOMComponent [ins] ._ currentElement.props
中提取{“ color”:“ blue”}
并将其附加到DOM的style
属性。在讨论“ setState()
触发的UI更新”时,我们将更详细地介绍此方法。搜索
*6
在本文中,当您需要检查首次使用此函数时。
6)实例化DOMLazyTree [ins]
。
7)_createInitialChildren
是另一个复杂的方法。 现在,我们只需要知道该方法从ReactDOMComponent [ins] ._ currentElement.children
中提取“ hello world”
并将其附加到DOM的textContent
即可。 在讨论“复合组件渲染”时,我们将更详细地介绍这种方法。 您可以搜寻
*7
然后,将DOMLazyTree [ins]
一直返回到 上一篇文章 mountComponentIntoNode()
中讨论的交叉点。
mountImageIntoNode()-将DOM装入容器节点
首先,让我复制上一篇文章的调用栈框架。
|=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 |
) _|_
现在执行:
_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
尽管看似复杂,但只有一行有效,它调用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
在此函数内部,唯一有效的一行是:
parentNode.insertBefore(tree.node, referenceNode);
这是另一个 HTML DOM API ,按照JSX开头的指示,将 DOMLazyTree [ins] .node
(即 h1 DOM element )插入#root
元素:
…
ReactDOM.render(
<h1 style={{“color”:”blue”}}>hello world</h1>,
document.getElementById(‘root’)
);
…
到目前为止的结论













—尾注—
在本系列文章中,我重点介绍非常原始的操作-h1组件挂载,尽管简单,它仍贯穿渲染过程的完整关键路径。 我认为这次快速的端到端之旅可以帮助读者建立初步的知识基础和信心,以进一步探索未知领域。 它将变得更加复杂和有趣。
尽管已尽全力模拟包括明确目标和具体成果的真实体验,但强烈建议您通过直接在Chrome中调试来尝试完整版的源代码。
React 16使用 fiber reconciler作为新架构。 如果您迫不及待想检查一下,请继续。Happy hacking!
(原文链接)Understanding The React Source Code - Initial Rendering (Simple Component) III
网友评论