【譯】了解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):

調(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é)論













—尾注—
在本系列文章中,我重點(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