摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結構如下經過編譯后,生成如下代碼構建頂層包裝組件跟普通元素渲染一樣,第一步先會執行創建為的。調用順序已在代碼中注釋。先看圖,這部分內容將在下回分解
前言
React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學習 React 源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。本文會大量用到原文中的例子,想體會原汁原味的感覺,推薦閱讀原文。
本系列文章基于 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 算法詳解
正文
前面三篇文章介紹了 React 是怎么渲染普通DOM元素的,如下圖所示。
紅線部分生成的markup實際上是一層一層往回傳,為了方便展示就直接跳過中間層級返回了。這張圖片跳過了事務(transaction)相關的調用,后面會有專門的文章介紹。
本篇開始介紹自定義組件是如何渲染的。
App 組件
將自定義組件命名為App,結構如下:
class App extends Component { constructor(props) { super(props); this.state = { desc: "start", }; } render() { return (); } } ReactDOM.render("Welcom to React"
{ this.state.desc }
, document.getElementById(‘root’) );
App 經過 Babel 編譯后,生成如下代碼:
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]
跟普通DOM元素渲染一樣,第一步先會執行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 實例。
然后就是 performInitialMount 根據 ReactElement 的類型來創建不同的對象。在渲染普通 DOM 元素的時候,這部會返回 ReactDOMComponent。但渲染自定義組件的時候,就不一樣了。
渲染普通 DOM 元素的調用關系如下圖所示,自定義組件的渲染調用關系見下文:
使用 App 創建ReactCompositeComponent[ins]
在 performInitialMount 這步,renderedElement 就是 ReactElement[1]:
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 操作了。先看圖,這部分內容將在下回分解~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98830.html
摘要:的和真正有效的都各只有一行代碼的調用棧如下這中間的函數調用邏輯很清晰,最終會走到這里這里的邏輯很簡單,如果不是數組,則調用回調函數如果是數組,則繼續調用自身,相當于深度優先遍歷。這里的回調函數就是中的這里直接調用,創建。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀...
摘要:在學習源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結從啟動到元素渲染到頁面,并不像看起來這么簡單,中間經歷了復雜的層級調用。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過...
摘要:依賴注入和控制反轉,這兩個詞經常一起出現。一句話表述他們之間的關系依賴注入是控制反轉的一種實現方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場景了。下一步就是創建具體的依賴內容,然后注入到需要的地方這里的等于這個對象。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級...
摘要:調用棧是這樣的這里生成的我們將其命名為,它將作為參數傳入到。整個的調用棧是這樣的組件間的層級結構是這樣的到此為止,頂層對象已經構造完畢,下一步就是調用來自的方法,進行頁面的渲染了。通過表達的結構最終會轉化為一個純對象,用于下一步的渲染。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言...
摘要:前言是一個十分龐大的庫,由于要同時考慮和,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學習源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常...
閱讀 1229·2021-11-24 09:39
閱讀 379·2019-08-30 14:12
閱讀 2592·2019-08-30 13:10
閱讀 2434·2019-08-30 12:44
閱讀 957·2019-08-29 16:31
閱讀 845·2019-08-29 13:10
閱讀 2434·2019-08-27 10:57
閱讀 3152·2019-08-26 13:57