上一章jsx語法是如何解析的講到了
<div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
複製程式碼
jsx語法是如何解析為虛擬dom
的,接下來我將聊聊虛擬dom
是如何掛載到真實dom
上的。 我讀的是React^15.6.2
的原始碼,因為最新的React^16.6.3
版本,引入了Fiber架構
,因為時間有限,Fiber
我暫時沒弄的太明白,但是它主要作用是優化元件的更新,所以不影響我們理解元件的掛載.好了,下面進入正題.
看看下面的程式碼如何執行的
import React from "./lib/react";
import {render} from "./lib/react-dom";
const Child = () => <div>
Child
</div>
class App extends React.Component {
constructor(props){
super(props);
this.state = {
name:"leiwuyi"
}
}
render(){
return (
<div>App</div>
)
}
}
render(<div className="leiwuyi">
<div>1</div>
<div>2</div>
<div>3</div>
<App ></App>
</div>, document.getElementById("root"));
複製程式碼
ReactDom.render()
其實是執行了_renderSubtreeIntoContainer
它接收了nextElement
, container
引數. 首選將container
這個Element
型別的節點包裝成Component
型別,
var nextWrappedElement = React.createElement(
TopLevelWrapper,
{
child: nextElement
}
);
然後執行ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
nextContext
)
複製程式碼
這個方法執行完畢,其實元件就已經掛載到root
節點上去了,來看看 ReactMount._renderNewRootComponent
方法
它主要分為二個部分
// 第一部分
var componentInstance = instantiateReactCompone
nt(
nextElement,
false
);
// 第二部分
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context
);
複製程式碼
第一部分instantiateReactComponent
instantiateReactComponent
依據不同的型別產生不同的例項,每個例項都具有mountComponent方法核心方法.
型別 | 對應方法 |
---|---|
null 或undefined |
ReactEmptyComponent |
元素型別 | ReactHostComponent.createInternalComponent( element) |
component 的型別 |
ReactCompositeComponent |
文字型別 | `ReactHostComponent.createInstanceForText(node) |
ReactHostComponent.createInternalComponent( element)
最終是執行的ReactDOMComponent
.
第二部分 MountComponentIntoNode
就是通過事務的形式來執行mountComponentIntoNode
方法
而mountComponentIntoNode
方法最終是執行例項的mountComponent
方法。
該方法會產生對應的真實dom
節點markup
;
產生之後然後通過setInnerHTML(container, markup)
;插入到container
裡面.
簡單的講就是拿到真實dom
插入到container
裡面去,這段程式碼執行完畢真實dom
就掛載完成了.
具體的實現過程
在前面·nextElement
包裝成Component
型別·,所以最終產生的例項的原型物件是ReactCompositeComponent
例項有了那麼接下來就是執行 componentInstance.mountComponent方法
;
該方法執行的ReactCompositeComponent.mountComponent
接下來看看該方法到底做了什麼. 下面我就幾個核心方法進行一個說明.
ReactCompositeComponent.mountComponent
第一個方法 例項化元件
this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue
)
產生元件的例項 如 <App > ---> new App() 就是我們常在元件中使用的this物件
第二個方法
拿到元件對應的真實dom
markup = this.performInitialMount(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context
);
複製程式碼
具體來看看第二個方法
this.performInitialMount
// 呼叫this.render或者func() 拿到虛擬dom,
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
// 拿到一個element型別的componentInstance(上文中有的)
var child = this._instantiateReactComponent(
renderedElement,
nodeType !==
ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
// 拿到真實dom
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID
);
複製程式碼
這裡面的ReactReconciler.mountComponent
其實是呼叫的ReactDOMComponet.mountComponent
方法。
所以this.performInitialMount
簡單的講就是 拿到元件的虛擬dom
,然後獲取它對應的componentInstance
例項然後執行ReactDOMComponet.mountComponent
拿到真實dom
所以我們接下來要看看ReactDOMComponet.mountComponent
方法的實現過程
ReactDOMComponet.mountComponent
函式前面一部分其實對tag
的型別進行了處理 我們直接略過把tag
當做div
建立了一個div節點
el = ownerDocument.createElement(
this._currentElement.type
);
// 設定該節點的屬性
this._updateDOMProperties(
null,
props,
transaction
);
// 構建它孩子的真實dom然後插入到該節點中去
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(
transaction,
props,
context,
lazyTree
);
所以執行之後lazyTree就是一個完整的真實dom節點
複製程式碼
我們來看看 this._createInitialChildren
方法,
核心程式碼在這裡
var mountImages = this.mountChildren(
childrenToUse,
transaction,
context
);
執行的是
var children = this._reconcilerInstantiateChildren(
nestedChildren,
transaction,
context
);
// 產生child對應的例項
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
var selfDebugID = 0;
if (
"development" !== "production"
) {
selfDebugID = getDebugID(this);
}
var mountImage = ReactReconciler.mountComponent(
child,
transaction,
this,
this._hostContainerInfo,
context,
selfDebugID
);
child._mountIndex = index++;
mountImages.push(mountImage);
}
}
複製程式碼
看到這裡,就已經很明顯這是一個深度優先遍歷;它的child
又產生了一個例項然後執行 mountComponent
方法拿到真實dom
,直到拿到最後一級孩子的真實dom
然後不斷向上遞迴插入父級。直到插入到最頂層這樣就拿到了Component
的真實dom
。最後插入到 container
容器裡面.
下一章將聊聊React
的生命週期