摘要:的和真正有效的都各只有一行代碼的調(diào)用棧如下這中間的函數(shù)調(diào)用邏輯很清晰,最終會走到這里這里的邏輯很簡單,如果不是數(shù)組,則調(diào)用回調(diào)函數(shù)如果是數(shù)組,則繼續(xù)調(diào)用自身,相當(dāng)于深度優(yōu)先遍歷。這里的回調(diào)函數(shù)就是中的這里直接調(diào)用,創(chuàng)建。
前言
React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學(xué)習(xí) React 源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。本文會大量用到原文中的例子,想體會原汁原味的感覺,推薦閱讀原文。
本系列文章基于 React 15.4.2 ,以下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務(wù) - Part 1
React 源碼深度解讀(八):事務(wù) - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解
正文
上一篇文章中,我們講解到ReactCompositeComponent[ins]被初始化后,App[ins]的 render 方法被調(diào)用,生成 ReactElement 樹,然后對應(yīng)的ReactDOMComponent[6]被返回。下面我們來看看這個ReactDOMComponent[6]是如何轉(zhuǎn)化為 DOM 樹的。
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) { ... // 這里會調(diào)用 App 實例的 render 方法,而 render 的返回值是 React.createElement 的嵌套調(diào)用。 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; },
ReactDOMComponent[6].mountComponent
ReactReconciler.mountComponent 會觸發(fā)ReactDOMComponent[6]的 mountComponent 方法,調(diào)用棧如下:
... |~mountComponentIntoNode() | |-ReactReconciler.mountComponent() | |-ReactCompositeComponent[T].mountComponent() | |-ReactCompositeComponent[T].performInitialMount() upper half |-ReactReconciler.mountComponent() | |-ReactCompositeComponent[ins].mountComponent() | |-this.performInitialMount() | |-this._renderValidatedComponent() | |-instantiateReactComponent() _|_ (we are here) | |-ReactDOMComponent[6].mountComponent( | transaction, // scr: -----> not of interest | hostParent, // scr: -----> null | hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins] lower half context // scr: -----> not of interest | ) | ...
mountComponent: function ( transaction, hostParent, hostContainerInfo, context ) { ... var mountImage; if (transaction.useCreateElement) { var ownerDocument = hostContainerInfo._ownerDocument; ... // 創(chuàng)建 div 元素 el = ownerDocument.createElement(this._currentElement.type); ... // 設(shè)置 attributes if (!this._hostParent) { DOMPropertyOperations.setAttributeForRoot(el); } // 設(shè)置 properties this._updateDOMProperties(null, props, transaction); // 構(gòu)造 DOM 樹 var lazyTree = DOMLazyTree(el); // 遍歷子節(jié)點并創(chuàng)建 DOM 結(jié)點 this._createInitialChildren(transaction, props, context, lazyTree); mountImage = lazyTree; } ... return mountImage; }
這里主要做的事情有3部分:
創(chuàng)建 DOM 元素
設(shè)置 attributes 和 properties
遍歷子元素并重復(fù)上述過程
這時候的數(shù)據(jù)結(jié)構(gòu)如下:
流程圖:
_createInitialChildren 遍歷子節(jié)點并創(chuàng)建 DOM 結(jié)點
下面來看一下 _createInitialChildren 的細(xì)節(jié):
_createInitialChildren: function (transaction, props, context, lazyTree) { // Intentional use of != to avoid catching zero/false. var innerHTML = props.dangerouslySetInnerHTML; if (innerHTML != null) { if (innerHTML.__html != null) { DOMLazyTree.queueHTML(lazyTree, innerHTML.__html); } } else { // 如果是 string 或者 number,返回 true var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null; var childrenToUse = contentToUse != null ? null : props.children; // 直接渲染字符串 if (contentToUse != null) { // Avoid setting textContent when the text is empty. In IE11 setting // textContent on a text area will cause the placeholder to not // show within the textarea until it has been focused and blurred again. // https://github.com/facebook/react/issues/6731#issuecomment-254874553 if (contentToUse !== "") { DOMLazyTree.queueText(lazyTree, contentToUse); } } // 渲染子節(jié)點 else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, transaction, context ); for (var i = 0; i < mountImages.length; i++) { DOMLazyTree.queueChild(lazyTree, mountImages[i]); } } } },
這部分代碼十分好懂,就 3 條分支:
設(shè)置了 dangerouslySetInnerHTML 屬性,直接渲染 HTML
子節(jié)點類型為 string 或 number,渲染字符
其它情況就需要將 ReactElement 轉(zhuǎn)換成 ReactDOMComponent 或 ReactCompositeComponent 作進一步的渲染。
DOMLazyTree 的 queueText 和 queueChild 真正有效的都各只有一行代碼:
function queueText(tree, text) { if (enableLazy) { // scr: NO, I mean, false ... } else { setTextContent(tree.node, text); } } var setTextContent = function (node, text) { if (text) { var firstChild = node.firstChild; if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { // scr: false ... } } node.textContent = text; // scr: the only effective line }; function queueChild(parentTree, childTree) { if (enableLazy) { // scr: again, false ... } else { parentTree.node.appendChild(childTree.node); } }
mountChildren 的調(diào)用棧如下:
ReactDOMComponent[6].mountComponent() <-------------------------| (we are here) | |-this._createInitialChildren() | ?{1} | |-DOMLazyTree.queueText() | ?{2} | |-this.mountChildren() // scr: ---------------> 1)(a) | |-this._reconcilerInstantiateChildren() | |-ReactChildReconciler.instantiateChildren() | |-traverseAllChildren() | |-traverseAllChildrenImpl() <------|inner | |?traverseAllChildrenImpl() ------|recursion | |-instantiateChild() | |-instantiateReactComponent() | |?ReactDOMComponent.mountComponent() // scr: -> 1)(b)---| |?DOMLazyTree.queueChild() // scr: ---------------> 2)
這中間的函數(shù)調(diào)用邏輯很清晰,最終會走到 traverseAllChildrenImpl 這里:
function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext ) { var type = typeof children; if (type === "undefined" || type === "boolean") { // All of the above are perceived as null. children = null; } if (children === null || type === "string" || type === "number" || // The following is inlined from ReactElement. This means we can optimize // some checks. React Fiber also inlines this logic for similar purposes. (type === "object" && children.$$typeof === REACT_ELEMENT_TYPE)) { callback( traverseContext, children, // If it"s the only child, treat the name as if it was wrapped in an array // so that it"s consistent if the number of children grows. nameSoFar === "" ? SEPARATOR + getComponentKey(children, 0) : nameSoFar ); return 1; } var child; var nextName; var subtreeCount = 0; // Count of children found in the current subtree. var nextNamePrefix = nameSoFar === "" ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { ... } return subtreeCount; }
這里的邏輯很簡單,如果 children 不是數(shù)組,則調(diào)用回調(diào)函數(shù);如果是數(shù)組,則繼續(xù)調(diào)用自身,相當(dāng)于深度優(yōu)先遍歷。這里的回調(diào)函數(shù)就是 ReactChildReconciler 中的 instantiateChild:
function instantiateChild(childInstances, child, name, selfDebugID) { ... if (child != null && keyUnique) { childInstances[name] = instantiateReactComponent(child, true); } }
這里直接調(diào)用 instantiateReactComponent,創(chuàng)建ReactDOMComponent。所有的ReactDOMComponent的創(chuàng)建順序如下:
ReactDOMComponent[6].mountComponent() |-this._createInitialChildren() |-this.mountChildren() ... |?instantiateReactComponent()[4,5] |-ReactDOMComponent[5].mountComponent() |-this._createInitialChildren() |-node.textContent = text; // scr: [5] done |-ReactDOMComponent[4].mountComponent() |-this._createInitialChildren() |-this.mountChildren() ... |?instantiateReactComponent()[2,3] |-ReactDOMComponent[2].mountComponent() // scr: [2] done |-ReactDOMComponent[3].mountComponent() |-this._createInitialChildren() |-node.textContent = text; // scr: [3] done |?node[4].appendChild()[2,3] // scr: [4] done |?node[6].appendChild()[4,5] // scr: [6] done
完成的流程圖:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98912.html
摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結(jié)構(gòu)如下經(jīng)過編譯后,生成如下代碼構(gòu)建頂層包裝組件跟普通元素渲染一樣,第一步先會執(zhí)行創(chuàng)建為的。調(diào)用順序已在代碼中注釋。先看圖,這部分內(nèi)容將在下回分解 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非...
摘要:在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結(jié)從啟動到元素渲染到頁面,并不像看起來這么簡單,中間經(jīng)歷了復(fù)雜的層級調(diào)用。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過...
摘要:依賴注入和控制反轉(zhuǎn),這兩個詞經(jīng)常一起出現(xiàn)。一句話表述他們之間的關(guān)系依賴注入是控制反轉(zhuǎn)的一種實現(xiàn)方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場景了。下一步就是創(chuàng)建具體的依賴內(nèi)容,然后注入到需要的地方這里的等于這個對象。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級...
摘要:調(diào)用棧是這樣的這里生成的我們將其命名為,它將作為參數(shù)傳入到。整個的調(diào)用棧是這樣的組件間的層級結(jié)構(gòu)是這樣的到此為止,頂層對象已經(jīng)構(gòu)造完畢,下一步就是調(diào)用來自的方法,進行頁面的渲染了。通過表達(dá)的結(jié)構(gòu)最終會轉(zhuǎn)化為一個純對象,用于下一步的渲染。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言...
摘要:本文將要講解的調(diào)用棧是下面這個樣子的平臺無關(guān)相關(guān)如果看源碼,我們會留意到很多相關(guān)的代碼,我們暫時先將其忽略,會在后續(xù)的文章中進行講解。現(xiàn)在我們來看一下各實例間的關(guān)系目前為止的調(diào)用棧平臺無關(guān)相關(guān)下一篇講解三總結(jié)本文講解了轉(zhuǎn)化為的過程。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...
閱讀 2161·2021-10-08 10:15
閱讀 1191·2019-08-30 15:52
閱讀 519·2019-08-30 12:54
閱讀 1536·2019-08-29 15:10
閱讀 2690·2019-08-29 12:44
閱讀 3011·2019-08-29 12:28
閱讀 3356·2019-08-27 10:57
閱讀 2220·2019-08-26 12:24