React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript
本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java
前面三篇文章介紹了 React 是怎麼渲染普通DOM元素的,以下圖所示。node
紅線部分生成的markup
其實是一層一層往回傳,爲了方便展現就直接跳過中間層級返回了。這張圖片跳過了事務(transaction)相關的調用,後面會有專門的文章介紹。算法
本篇開始介紹自定義組件是如何渲染的。segmentfault
將自定義組件命名爲App
,結構以下:服務器
class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return ( <div className="App"> <div className="App-header"> <img src="main.jpg" className="App-logo" alt="logo" /> <h1> "Welcom to React" </h1> </div> <p className="App-intro"> { this.state.desc } </p> </div> ); } } ReactDOM.render( <App />, document.getElementById(‘root’) );
App 通過 Babel 編譯後,生成以下代碼:app
class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return React.createElement( 'div', { className: 'App' }, React.createElement( 'div', { className: 'App-header' }, React.createElement( 'img', { src: "main.jpg", className: 'App-logo', alt: 'logo' } ), React.createElement( 'h1', null, ' "Welcom to React" ' ) ), React.createElement( 'p', { className: 'App-intro' }, this.state.desc ) ); } } ReactDOM.render( React.createElement(App, null), document.getElementById(‘root’) );
ReactCompositeComponent[T]
React.createElement
建立 type 爲 App 的 ReactElement[1]
。_renderSubtreeIntoContainer
裏面建立 type 爲 TopLevelWrapper 的 ReactElement[2]
。instantiateReactComponent
建立包裝元素 ReactCompositeComponent[T]
。調用關係以下圖所示:學習
ReactCompositeComponent[T]
mountComponentIntoNode
時,ReactDOMContainerInfo[ins]
會被建立並傳給ReactReconciler
。ReactReconciler
會調用ReactCompositeComponent[T]
的 mountComponent 建立 TopLevelWrapper 實例。渲染普通 DOM 元素的調用關係以下圖所示,自定義組件的渲染調用關係見下文:this
ReactCompositeComponent[ins]
在 performInitialMount 這步,renderedElement 就是 ReactElement[1]
:spa
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) { ... // 這裏會調用 TopLevelWrapper 實例的 render 方法,獲得 ReactElement[1] if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } ... // 返回 ReactCompositeComponent[ins] var child = this._instantiateReactComponent( renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent( child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID ); return markup; },
這步與第二篇的 performInitialMount 很類似,惟一區別就是渲染普通 DOM 元素返回的是ReactDOMComponent
,而渲染自定義組件返回的是包裝好的自定義組件ReactCompositeComponent[ins]
。
調用關係以下圖所示:
ReactCompositeComponent[ins]
在 performInitialMount 的後半部分,ReactReconciler.mountComponent 實際上會調用 ReactCompositeComponent[ins] 的 mountComponent。這裏的關鍵代碼是
... // 建立 App 組件的實例 var inst = this._constructComponent( doConstruct, publicProps, publicContext, updateQueue ); ...
通過這步後,ReactCompositeComponent[ins]._instance 等於 App[ins]。像以前同樣,mountComponent 又會調用自身的 performInitialMount:
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) { ... // 這裏會調用 App 實例的 render 方法,而 render 的返回值是 React.createElement 的嵌套調用。 if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } ... // 返回 ReactDOMComponent[6] var child = this._instantiateReactComponent( renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent( child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID ); return markup; },
React.createElement 的嵌套調用是指:
render() { return React.createElement( // scr: -----------> 5) 'div', { className: 'App' }, React.createElement( // scr: -----------> 3) 'div', { className: 'App-header' }, React.createElement( // scr: -----------> 1) 'img', { src: "main.jpg", className: 'App-logo', alt: 'logo' } ), React.createElement( // scr: -----------> 2) 'h1', null, ' "Welcom to React" ' ) ), React.createElement( // scr: -----------> 4) 'p', { className: 'App-intro' }, this.state.desc ) ); }
這裏 React.createElement 的調用順序是先調用做爲參數的 children,再調用父級。調用順序已在代碼中註釋。
接下來的 _instantiateReactComponent 會返回ReactDOMComponent
,就觸及到真正的 DOM 操做了。先看圖,這部份內容將在下回分解~