React原始碼解析(2):元件的掛載
上一章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
的生命週期