摘要:爭(zhēng)取把源碼剖析透學(xué)習(xí)透。除了用戶定義的復(fù)合組件外元素還可能表示特定于平臺(tái)的主機(jī)組件。裝載的具體結(jié)果有時(shí)在源代碼中稱為裝載映像取決于渲染器,可能為節(jié)點(diǎn)字符串服務(wù)器或表示本機(jī)視圖的數(shù)值。其所缺少的關(guān)鍵部分是對(duì)更新的支持。
關(guān)于源碼解讀的系列文章,可以關(guān)注我的github的這個(gè)倉(cāng)庫(kù), 現(xiàn)在才剛剛寫(xiě),后續(xù)有空就寫(xiě)點(diǎn)。爭(zhēng)取把react源碼剖析透學(xué)習(xí)透。有不正確的地方希望大家?guī)兔χ刚4蠹一ハ鄬W(xué)習(xí),共同進(jìn)步。
本篇文章是官方文檔的翻譯,英文原文請(qǐng)?jiān)L問(wèn)官網(wǎng)
這個(gè)章節(jié)是stack reconciler的一些實(shí)現(xiàn)說(shuō)明.
它的技術(shù)性很強(qiáng)并假定你能完全理解React的公開(kāi)API,以及它是如何劃分為核心、渲染器和協(xié)調(diào)器的。如果你對(duì)React代碼不是很熟悉,請(qǐng)先閱讀代碼概覽。
它還假定你能夠理解React組件、實(shí)例和元素的區(qū)別。
Stack reconciler 被用在React 15 以及更早的版本中, 它在源代碼中的位置是src/renderers/shared/stack/reconciler.
視頻:從零開(kāi)始構(gòu)建ReactPaul O"Shannessy給出了一個(gè)關(guān)于從零開(kāi)始構(gòu)建React的討論,在很大程度上對(duì)本文檔給予了啟發(fā)。
本文檔與上邊的視頻都是對(duì)實(shí)際代碼庫(kù)的簡(jiǎn)化,因此你可以通過(guò)熟悉兩者來(lái)更好地理解。
概述協(xié)調(diào)器本身沒(méi)有公共 API. 但是諸如React DOM 和React Native的渲染器使用它依據(jù)用戶所編寫(xiě)的React組件來(lái)有效地更新用戶界面.
以遞歸過(guò)程的形式裝載讓我們考慮首次裝載組件的情形:
ReactDOM.render(, rootEl);
React DOM會(huì)將
console.log(); // { type: App, props: {} }
協(xié)調(diào)器(reconciler)會(huì)檢查 App是類還是函數(shù)。如果 App 是函數(shù),協(xié)調(diào)器會(huì)調(diào)用App(props)來(lái)獲取所渲染的元素。如果App是類,協(xié)調(diào)器則會(huì)使用new App(props)創(chuàng)建一個(gè)App實(shí)例,調(diào)用 componentWillMount() 生命周期方法,進(jìn)而調(diào)用 render() 方法來(lái)獲取所渲染的元素。無(wú)論如何,協(xié)調(diào)器都會(huì)學(xué)習(xí)App元素的“渲染行為”。
此過(guò)程是遞歸的。App 可能渲染為
你可以通過(guò)如下偽代碼來(lái)理解該過(guò)程:
function isClass(type) { // React.Component的子類都會(huì)含有這一標(biāo)志 return ( Boolean(type.prototype) && Boolean(type.prototype.isReactComponent) ); } // This function takes a React element (e.g.) // and returns a DOM or Native node representing the mounted tree. // 此函數(shù)讀取一個(gè)React元素(例如 ) // 并返回一個(gè)表達(dá)所裝載樹(shù)的DOM或內(nèi)部節(jié)點(diǎn)。 function mount(element) { var type = element.type; var props = element.props; // 我們以此判斷所渲染元素: // 是以函數(shù)型運(yùn)行該類型 // 還是創(chuàng)建新實(shí)例并調(diào)用render()。 var renderedElement; if (isClass(type)) { // Component class var publicInstance = new type(props); // Set the props publicInstance.props = props; // Call the lifecycle if necessary if (publicInstance.componentWillMount) { publicInstance.componentWillMount(); } // 調(diào)用render()以獲取所渲染元素 renderedElement = publicInstance.render(); } else { // 組件函數(shù) renderedElement = type(props); } // 該過(guò)程是遞歸實(shí)現(xiàn),原因在于組件可能返回一個(gè)其它組件類型的元素。 return mount(renderedElement); // 注意:該實(shí)現(xiàn)不完整,且將無(wú)窮遞歸! 它只處理 或等元素。尚不處理或等元素。 } var rootEl = document.getElementById("root"); var node = mount( ); rootEl.appendChild(node);
注意:這的確是一段偽代碼。它與真實(shí)的實(shí)現(xiàn)不同。它會(huì)導(dǎo)致棧溢出,因?yàn)槲覀冞€沒(méi)有討論何時(shí)停止遞歸。
讓我們回顧一下上面示例中的幾個(gè)關(guān)鍵概念:
React元素是表示組件類型(例如 App)與屬性的普通對(duì)象。
用戶定義組件(例如 App)可以為類或者函數(shù),但它們都會(huì)被“渲染為”元素。
“裝載”(Mounting)是一個(gè)遞歸過(guò)程,當(dāng)給定頂級(jí)React元素(例如
該過(guò)程將沒(méi)有任何意義,如果最終沒(méi)有渲染內(nèi)容到屏幕上。
除了用戶定義的(“復(fù)合”)組件外, React元素還可能表示特定于平臺(tái)的(“主機(jī)”)組件。例如,Button可能會(huì)從其渲染方法中返回 。
如果元素的type屬性是一個(gè)字符串,即表示我們正在處理一個(gè)主機(jī)元素(host element):
console.log(); // { type: "div", props: {} }
主機(jī)元素(host elements)不存在關(guān)聯(lián)的用戶定義代碼。
當(dāng)協(xié)調(diào)器遇到主機(jī)元素(host element)時(shí),它會(huì)讓渲染器(renderer)裝載它(mounting)。例如,React DOM將會(huì)創(chuàng)建一個(gè)DOM節(jié)點(diǎn)。
如果主機(jī)元素(host element)有子級(jí),協(xié)調(diào)器(reconciler)則會(huì)用上述相同算法遞歸地將它們裝載。而不管子級(jí)是主機(jī)元素(如
由子級(jí)組件生成的DOM節(jié)點(diǎn)將被追加到DOM父節(jié)點(diǎn),同時(shí)整的DOM結(jié)構(gòu)會(huì)被遞歸裝配。
注意:協(xié)調(diào)器本身(reconciler)并不與DOM捆綁。裝載(mounting)的具體結(jié)果(有時(shí)在源代碼中稱為“裝載映像”)取決于渲染器(renderer),可能為 DOM節(jié)點(diǎn)(React DOM)、字符串(React DOM服務(wù)器)或表示本機(jī)視圖的數(shù)值(React Native)。
我們來(lái)擴(kuò)展一下代碼,以處理主機(jī)元素(host elements):
function isClass(type) { // React.Component 子類含有這一標(biāo)志 return ( Boolean(type.prototype) && Boolean(type.prototype.isReactComponent) ); } // 該函數(shù)僅處理含復(fù)合類型的元素。 例如,它處理和,但不處理。 function mountComposite(element) { var type = element.type; var props = element.props; var renderedElement; if (isClass(type)) { // 組件類 var publicInstance = new type(props); // 設(shè)置屬性 publicInstance.props = props; // 若必要,則調(diào)用生命周期函數(shù) if (publicInstance.componentWillMount) { publicInstance.componentWillMount(); } renderedElement = publicInstance.render(); } else if (typeof type === "function") { // 組件函數(shù) renderedElement = type(props); } // 該過(guò)程是遞歸,一旦該元素為主機(jī)(如}而非復(fù)合(如 )時(shí),則逐漸結(jié)束 return mount(renderedElement); } // 該函數(shù)僅處理含主機(jī)類型的元素(handles elements with a host type)。 例如,它處理和但不處理 。 function mountHost(element) { var type = element.type; var props = element.props; var children = props.children || []; if (!Array.isArray(children)) { children = [children]; } children = children.filter(Boolean); // 該代碼塊不可出現(xiàn)在協(xié)調(diào)器(reconciler)中。 // 不同渲染器(renderers)可能會(huì)以不同方式初始化節(jié)點(diǎn)。 // 例如,React Native會(huì)生成iOS或Android視圖。 var node = document.createElement(type); Object.keys(props).forEach(propName => { if (propName !== "children") { node.setAttribute(propName, props[propName]); } }); // 裝載子節(jié)點(diǎn) children.forEach(childElement => { // 子節(jié)點(diǎn)有可能是主機(jī)元素(如)或復(fù)合元素(如). // 所以我們應(yīng)該遞歸的裝載 var childNode = mount(childElement); // 此行代碼仍是特定于渲染器的。不同的渲染器則會(huì)使用不同的方法 node.appendChild(childNode); }); // 返回DOM節(jié)點(diǎn)作為裝載結(jié)果 // 此處即為遞歸結(jié)束. return node; } function mount(element) { var type = element.type; if (typeof type === "function") { // 用戶定義的組件 return mountComposite(element); } else if (typeof type === "string") { // 平臺(tái)相關(guān)的組件,比如說(shuō)瀏覽器中的div,ios和安卓中的視圖 return mountHost(element); } } var rootEl = document.getElementById("root"); var node = mount( ); rootEl.appendChild(node);
該代碼能夠工作但仍與協(xié)調(diào)器(reconciler)的真正實(shí)現(xiàn)相差甚遠(yuǎn)。其所缺少的關(guān)鍵部分是對(duì)更新的支持。
介紹內(nèi)部實(shí)例React 的關(guān)鍵特征是您可以重新渲染所有內(nèi)容, 它不會(huì)重新創(chuàng)建 DOM 或重置狀態(tài):
ReactDOM.render(, rootEl); // 應(yīng)該重新使用現(xiàn)存的 DOM: ReactDOM.render( , rootEl);
但是, 上面的實(shí)現(xiàn)只知道如何裝載初始樹(shù)。它無(wú)法對(duì)其執(zhí)行更新, 因?yàn)樗鼪](méi)有存儲(chǔ)所有必需的信息, 例如所有 publicInstance ,
或者哪個(gè) DOM 節(jié)點(diǎn) 對(duì)應(yīng)于哪些組件。
堆棧協(xié)調(diào)(stack reconciler)的基本代碼是通過(guò)使 mount () 函數(shù)成為一個(gè)方法并將其放在類上來(lái)解決這一問(wèn)題。
這種方式有一些缺陷,但是目前代碼中仍然使用的是這種方式。不過(guò)目前我們也正在重寫(xiě)協(xié)調(diào)器(reconciler)
我們將創(chuàng)建兩個(gè)類: DOMComponent 和 CompositeComponent , 而不是多帶帶的 mountHost 和 mountComposite 函數(shù)。
兩個(gè)類都有一個(gè)接受 element 的構(gòu)造函數(shù), 以及一個(gè)能返回已裝入節(jié)點(diǎn)的 mount () 方法。我們將用一個(gè)能實(shí)例化正確類的工廠函數(shù)替換掉之前
例子里的mount函數(shù):
function instantiateComponent(element) { var type = element.type; if (typeof type === "function") { // 用戶自定義組件 return new CompositeComponent(element); } else if (typeof type === "string") { // 特定于平臺(tái)的組件 return new DOMComponent(element); } }
首先, 讓我們考慮如何實(shí)現(xiàn) CompositeComponent:
class CompositeComponent { constructor(element) { this.currentElement = element; this.renderedComponent = null; this.publicInstance = null; } getPublicInstance() { // 針對(duì)復(fù)合組合, 返回類的實(shí)例. return this.publicInstance; } mount() { var element = this.currentElement; var type = element.type; var props = element.props; var publicInstance; var renderedElement; if (isClass(type)) { // 組件類 publicInstance = new type(props); // 設(shè)置屬性 publicInstance.props = props; // 如果有必要,調(diào)用生命周期 if (publicInstance.componentWillMount) { publicInstance.componentWillMount(); } renderedElement = publicInstance.render(); } else if (typeof type === "function") { // Component function publicInstance = null; renderedElement = type(props); } // Save the public instance this.publicInstance = publicInstance; // 通過(guò)element實(shí)例化內(nèi)部的child實(shí)例,這個(gè)實(shí)例有可能是DOMComponent,比如 or // 也可能是CompositeComponent 比如說(shuō)or var renderedComponent = instantiateComponent(renderedElement); this.renderedComponent = renderedComponent; // 增加渲染輸出 return renderedComponent.mount(); } }
這與我們以前的 mountComposite() 實(shí)現(xiàn)沒(méi)有太大的不同, 但現(xiàn)在我們可以保存一些信息,
比如this.currentElement、this.renderedComponent 和 this.publicInstance ,這些保存的信息會(huì)在更新期間被使用。
請(qǐng)注意, CompositeComponent的實(shí)例與用戶提供的 element.type 的實(shí)例不是一回事。
CompositeComponent是我們的協(xié)調(diào)器(reconciler)的一個(gè)實(shí)現(xiàn)細(xì)節(jié), 從不向用戶公開(kāi)。
用戶自定義類是我們從 element.type 讀取的,并且通過(guò) CompositeComponent 創(chuàng)建它的一個(gè)實(shí)例。
為避免混亂,我們將CompositeComponent和DOMComponent的實(shí)例稱為“內(nèi)部實(shí)例”。
由于它們的存在, 我們可以將一些長(zhǎng)壽數(shù)據(jù)(ong-lived)與它們關(guān)聯(lián)起來(lái)。只有渲染器(renderer)和協(xié)調(diào)器(reconciler)知道它們的存在。
另一方面, 我們將用戶定義的類的實(shí)例稱為 "公共實(shí)例"(public instance)。公共實(shí)例是您在 render() 和自定義組件的其他方法中看到的 this
mountHost() 函數(shù)被重構(gòu)為 DOMComponent 類上的 mount()方法, 也看起來(lái)很熟悉:
class DOMComponent { constructor(element) { this.currentElement = element; this.renderedChildren = []; this.node = null; } getPublicInstance() { // For DOM components, only expose the DOM node. return this.node; } mount() { var element = this.currentElement; var type = element.type; var props = element.props; var children = props.children || []; if (!Array.isArray(children)) { children = [children]; } // Create and save the node var node = document.createElement(type); this.node = node; // Set the attributes Object.keys(props).forEach(propName => { if (propName !== "children") { node.setAttribute(propName, props[propName]); } }); // Create and save the contained children. // Each of them can be a DOMComponent or a CompositeComponent, // depending on whether the element type is a string or a function. var renderedChildren = children.map(instantiateComponent); this.renderedChildren = renderedChildren; // Collect DOM nodes they return on mount var childNodes = renderedChildren.map(child => child.mount()); childNodes.forEach(childNode => node.appendChild(childNode)); // Return the DOM node as mount result return node; } }
從 mountHost () 重構(gòu)后的主要區(qū)別在于, 我們現(xiàn)在將 this.node 和 this.renderedChildren 與內(nèi)部 DOM 組件實(shí)例相關(guān)聯(lián)。
我們還將使用它們?cè)趯?lái)應(yīng)用非破壞性更新。
因此, 每個(gè)內(nèi)部實(shí)例 (復(fù)合實(shí)例或主機(jī)實(shí)例)(composite or host) 現(xiàn)在都指向內(nèi)部的子實(shí)例。為幫助可視化, 如果功能 在 DOM 中, 您只會(huì)看到 內(nèi)部的復(fù)合實(shí)例需要存儲(chǔ)下面的信息: 當(dāng)前元素(The current element). 如果元素類型是類, 則將類實(shí)例化并存為公共實(shí)例(The public instance if element type is a class). 一個(gè)通過(guò)運(yùn)行render()之后并傳入工廠函數(shù)而得到的內(nèi)部實(shí)例(renderedComponent)。它可以是一個(gè)DOMComponent或一個(gè)CompositeComponent。 內(nèi)部的主機(jī)實(shí)例需要存儲(chǔ)下面的信息: 當(dāng)前元素(The current element). DOM 節(jié)點(diǎn)(The DOM node). 所有的內(nèi)部子實(shí)例,他們可以是 DOMComponent or a CompositeComponent。(All the child internal instances. Each of them can be either a DOMComponent or a CompositeComponent). 如果你很難想象一個(gè)內(nèi)部的實(shí)例樹(shù)是如何在更復(fù)雜的應(yīng)用中構(gòu)建的, React DevTools)可以給出一個(gè)非常接近的近似,因?yàn)樗怀鲲@示了帶有灰色的主機(jī)實(shí)例,以及用紫色表示的組合實(shí)例: 為了完成這個(gè)重構(gòu),我們將引入一個(gè)函數(shù),它將一個(gè)完整的樹(shù)掛載到一個(gè)容器節(jié)點(diǎn),就像ReactDOM.render()。它返回一個(gè)公共實(shí)例,也類似于 ReactDOM.render(): 現(xiàn)在,我們有了保存有它們的子節(jié)點(diǎn)和DOM節(jié)點(diǎn)的內(nèi)部實(shí)例,我們可以實(shí)現(xiàn)卸載。對(duì)于一個(gè)復(fù)合組件(composite component),卸載將調(diào)用一個(gè)生命周期鉤子然后遞歸進(jìn)行。 對(duì)于DOMComponent,卸載操作讓每個(gè)孩子進(jìn)行卸載: 在實(shí)踐中,卸載DOM組件也會(huì)刪除事件偵聽(tīng)器并清除一些緩存,為了便于理解,我們暫時(shí)跳過(guò)這些細(xì)節(jié)。 現(xiàn)在我們可以添加一個(gè)頂級(jí)函數(shù),叫作unmountTree(containerNode),它與ReactDOM.unmountComponentAtNode()類似: 為了使其工作,我們需要從一個(gè)DOM節(jié)點(diǎn)讀取一個(gè)內(nèi)部根實(shí)例。我們將修改 mountTree() 以將 _internalInstance 屬性添加到DOM 根節(jié)點(diǎn)。 現(xiàn)在,可以反復(fù)運(yùn)行unmountTree()或者 mountTree(),清除舊樹(shù)并且在組件上運(yùn)行 componentWillUnmount() 生命周期鉤子。 在上一節(jié)中,我們實(shí)現(xiàn)了卸載。然而,如果每個(gè)組件的prop的變動(dòng)都要卸載并掛載整個(gè)樹(shù),這是不可接受的。幸好我們?cè)O(shè)計(jì)了協(xié)調(diào)器。 我們將用一種方法擴(kuò)展我們的內(nèi)部實(shí)例。 它的工作是做任何必要的工作,以使組件(及其任何子節(jié)點(diǎn)) 能夠根據(jù) nextElement 提供的信息保持信息為最新?tīng)顟B(tài)。 這是經(jīng)常被描述為"virtual DOM diffing"的部分,盡管真正發(fā)生的是我們遞歸地遍歷內(nèi)部樹(shù),并讓每個(gè)內(nèi)部實(shí)例接收到更新指令。 當(dāng)一個(gè)復(fù)合組件接收到一個(gè)新元素(element)時(shí),我們運(yùn)行componentWillUpdate()生命周期鉤子。 然后,我們使用新的props重新render組件,并獲得下一個(gè)render的元素(rendered element): 下一步,我們可以看一下渲染元素的type。如果自從上次渲染,type 沒(méi)有被改變,組件接下來(lái)可以被適當(dāng)更新。 例如,如果它第一次返回 ,并且第二次返回 ,我們可以告訴內(nèi)部實(shí)例去 receive() 下一個(gè)元素: 但是,如果下一個(gè)被渲染元素和前一個(gè)相比有一個(gè)不同的type ,我們不能更新內(nèi)部實(shí)例。因?yàn)橐粋€(gè) 相反,我們必須卸載現(xiàn)有的內(nèi)部實(shí)例并掛載對(duì)應(yīng)于渲染的元素類型的新實(shí)例。 總而言之,當(dāng)一個(gè)復(fù)合組件(composite component)接收到一個(gè)新元素時(shí),它可能會(huì)將更新委托給其渲染的內(nèi)部實(shí)例((rendered internal instance), 另一種情況下,組件將重新掛載而不是接收一個(gè)元素,并且這發(fā)生在元素的key變化時(shí)。本文檔中,我們不討論key 處理,因?yàn)樗鼘⑹乖緩?fù)雜的教程更加復(fù)雜。 注意,我們需要添加一個(gè)叫作getHostNode()的新方法到內(nèi)部實(shí)例(internal instance),以便可以定位特定于平臺(tái)的節(jié)點(diǎn)并在更新期間替換它。 主機(jī)組件實(shí)現(xiàn)(例如DOMComponent), 是以不同方式更新.當(dāng)它們接收到一個(gè)元素時(shí),它們需要更新底層特定于平臺(tái)的視圖。在 React DOM 中,這意味著更新 DOM 屬性: 接下來(lái),主機(jī)組件需要更新它們的子元素。與復(fù)合組件不同的是,它們可能包含多個(gè)子元素。 在這個(gè)簡(jiǎn)化的例子中,我們使用一個(gè)內(nèi)部實(shí)例的數(shù)組并對(duì)其進(jìn)行迭代,是更新或替換內(nèi)部實(shí)例,這取決于接收到的type是否與之前的type匹配。 我們?cè)诹斜碇惺占疍OM操作,這樣我們就可以批量地執(zhí)行它們。 作為最后一步,我們執(zhí)行DOM操作。還是那句話,真正的協(xié)調(diào)器(reconciler)代碼更復(fù)雜,因?yàn)樗€能處理移動(dòng): 這是用來(lái)更新主機(jī)組件(host components)的。 現(xiàn)在 CompositeComponent 與 DOMComponent 都實(shí)現(xiàn)了 receive(nextElement) 方法, 現(xiàn)在調(diào)用 mountTree()兩次,同樣的類型不會(huì)先卸載再裝載了: These are the basics of how React works internally. 與真正的代碼庫(kù)相比,這個(gè)文檔被簡(jiǎn)化了。有一些重要的方面我們沒(méi)有提到: 組件可以渲染null,而且,協(xié)調(diào)器(reconciler)可以處理數(shù)組中的“空槽(empty slots)”并顯示輸出。 協(xié)調(diào)器(reconciler)可以從元素中讀取 key ,并且用它來(lái)建立在一個(gè)數(shù)組中內(nèi)部實(shí)例與元素的對(duì)應(yīng)關(guān)系。實(shí)際的 React 實(shí)現(xiàn)的大部分復(fù)雜性與此相關(guān)。 除了復(fù)合和主機(jī)內(nèi)部實(shí)例類之外,還存在用于“文本”和“空”組件的類。它們表示文本節(jié)點(diǎn)和通過(guò)渲染 null得到的“空槽”。 渲染器(Renderers)使用injection
將主機(jī)內(nèi)部類傳遞給協(xié)調(diào)器(reconciler)。例如,React DOM 告訴協(xié)調(diào)器使用 ReactDOMComponent 作為主機(jī)內(nèi)部實(shí)現(xiàn)實(shí)例。 更新子列表的邏輯被提取到一個(gè)名為 ReactMultiChild 的mixin中,它被主機(jī)內(nèi)部實(shí)例類實(shí)現(xiàn)在 React DOM和 React Native時(shí)都使用。 協(xié)調(diào)器也實(shí)現(xiàn)了在復(fù)合組件(composite components)中支持setState()。事件處理程序內(nèi)部的多個(gè)更新將被打包成一個(gè)單一的更新。 協(xié)調(diào)器(reconciler)還負(fù)責(zé)復(fù)合組件和主機(jī)節(jié)點(diǎn)的refs。 在DOM準(zhǔn)備好之后調(diào)用的生命周期鉤子,例如 componentDidMount() 和 componentDidUpdate(),收集到“回調(diào)隊(duì)列”,并在單個(gè)批處理中執(zhí)行。 React 將當(dāng)前更新的信息放入一個(gè)名為“事務(wù)”的內(nèi)部對(duì)象中。事務(wù)對(duì)于跟蹤掛起的生命周期鉤子的隊(duì)列、 為了warning而嵌套的當(dāng)前DOM(the current DOM nesting for the warnings)以及任何“全局”到特定的更新都是有用的。 在 ReactMount 中可以查看此教程中類似 mountTree() 和 unmountTree() 的代碼. 它負(fù)責(zé)裝載(mounting)和卸載(unmounting)頂級(jí)組件。 ReactDOMComponent 在教程中與DOMComponent等同. 它實(shí)現(xiàn)了 React DOM渲染器(renderer)的主機(jī)組件類(host component class。 ReactCompositeComponent 在教程中與 CompositeComponent 等同. 它處理調(diào)用用戶定義的組件并維護(hù)它們的狀態(tài)。
instantiateReactComponent ReactReconciler 是一個(gè)具有 mountComponent(), receiveComponent(), 和 unmountComponent() 方法的封裝. ReactChildReconciler 根據(jù)元素的 key ,實(shí)現(xiàn)了mounting、updating和unmounting的邏輯. ReactMultiChild 獨(dú)立于渲染器的操作隊(duì)列,實(shí)現(xiàn)了處理child的插入、刪除和移動(dòng) 由于遺留的原因 mount(), receive(), and unmount() 被稱作 mountComponent(), receiveComponent(), and unmountComponent() 但是他們卻接收elements 內(nèi)部實(shí)例的屬性以一個(gè)下劃線開(kāi)始, 例如, _currentElement. 在整個(gè)代碼庫(kù)中,它們被認(rèn)為是只讀的公共字段。 堆棧協(xié)調(diào)器具有固有的局限性, 如同步和無(wú)法中斷工作或分割成區(qū)塊。 閱讀next section以了解有關(guān)協(xié)調(diào)器的當(dāng)前實(shí)現(xiàn)的詳細(xì)信息。 文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/95238.html 摘要:因?yàn)榘姹緦⒄嬲龔U棄這三生命周期到目前為止,的渲染機(jī)制遵循同步渲染首次渲染,更新時(shí)更新時(shí)卸載時(shí)期間每個(gè)周期函數(shù)各司其職,輸入輸出都是可預(yù)測(cè),一路下來(lái)很順暢。通過(guò)進(jìn)一步觀察可以發(fā)現(xiàn),預(yù)廢棄的三個(gè)生命周期函數(shù)都發(fā)生在虛擬的構(gòu)建期間,也就是之前。
showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300);
背景
前段時(shí)間準(zhǔn)備前端招聘事項(xiàng)... 摘要:每次都信誓旦旦的給自己立下要好好學(xué)習(xí)源碼的,結(jié)果都是因?yàn)槟硞€(gè)地方卡住了,或是其他原因沒(méi)看多少就放棄了。這次又給自己立個(gè)堅(jiān)持看完源碼。我看的源碼版本是。本篇文章是官方文檔里邊的一篇文章的翻譯,原文地址。
每次都信誓旦旦的給自己立下要好好學(xué)習(xí)react源碼的flag,結(jié)果都是因?yàn)槟硞€(gè)地方卡住了,或是其他原因沒(méi)看多少就放棄了。這次又給自己立個(gè)flag-堅(jiān)持看完react源碼。為了敦促自己,特... 摘要:表示調(diào)用棧在下一將要執(zhí)行的任務(wù)。兩方性能解藥我們一般有兩種方案突破上文提到的瓶頸將耗時(shí)高成本高易阻塞的長(zhǎng)任務(wù)切片,分成子任務(wù),并異步執(zhí)行這樣一來(lái),這些子任務(wù)會(huì)在不同的周期執(zhí)行,進(jìn)而主線程就可以在子任務(wù)間隙當(dāng)中執(zhí)行更新操作。
showImg(https://segmentfault.com/img/remote/1460000016008111);
性能一直以來(lái)是前端開(kāi)發(fā)中非常重要的話題... 摘要:如果運(yùn)算持續(xù)占用主線程,頁(yè)面就沒(méi)法得到及時(shí)的更新。三解題思路解決主線程長(zhǎng)時(shí)間被運(yùn)算占用這一問(wèn)題的基本思路,是將運(yùn)算切割為多個(gè)步驟,分批完成。這顆新樹(shù)每生成一個(gè)新的節(jié)點(diǎn),都會(huì)將控制權(quán)交回給主線程,去檢查有沒(méi)有優(yōu)先級(jí)更高的任務(wù)需要執(zhí)行。
歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo);
一、前言
在...[object CompositeComponent] {
currentElement:
function mountTree(element, containerNode) {
// 創(chuàng)建頂級(jí)內(nèi)部實(shí)例
var rootComponent = instantiateComponent(element);
// 將頂級(jí)組件裝載到容器中
var node = rootComponent.mount();
containerNode.appendChild(node);
// 返回它所提供的公共實(shí)例
var publicInstance = rootComponent.getPublicInstance();
return publicInstance;
}
var rootEl = document.getElementById("root");
mountTree(
卸載(Unmounting)
class CompositeComponent {
// ...
unmount() {
// Call the lifecycle hook if necessary
var publicInstance = this.publicInstance;
if (publicInstance) {
if (publicInstance.componentWillUnmount) {
publicInstance.componentWillUnmount();
}
}
// Unmount the single rendered component
var renderedComponent = this.renderedComponent;
renderedComponent.unmount();
}
}
class DOMComponent {
// ...
unmount() {
// Unmount all the children
var renderedChildren = this.renderedChildren;
renderedChildren.forEach(child => child.unmount());
}
}
function unmountTree(containerNode) {
// Read the internal instance from a DOM node:
// (This doesn"t work yet, we will need to change mountTree() to store it.)
var node = containerNode.firstChild;
var rootComponent = node._internalInstance;
// Unmount the tree and clear the container
rootComponent.unmount();
containerNode.innerHTML = "";
}
我們也將教mountTree()去銷毀任何現(xiàn)存樹(shù),以便將來(lái)它可以被多次調(diào)用:function mountTree(element, containerNode) {
// Destroy any existing tree
if (containerNode.firstChild) {
unmountTree(containerNode);
}
// Create the top-level internal instance
var rootComponent = instantiateComponent(element);
// Mount the top-level component into the container
var node = rootComponent.mount();
containerNode.appendChild(node);
// Save a reference to the internal instance
node._internalInstance = rootComponent;
// Return the public instance it provides
var publicInstance = rootComponent.getPublicInstance();
return publicInstance;
}
協(xié)調(diào)器(reconciler)的目標(biāo)是重用已存在的實(shí)例,以便保留DOM和狀態(tài):var rootEl = document.getElementById("root");
mountTree(
除了 mount()和 unmount()。DOMComponent和 CompositeComponent將實(shí)現(xiàn)一個(gè)新的方法,它叫作 receive(nextElement):class CompositeComponent {
// ...
receive(nextElement) {
// ...
}
}
class DOMComponent {
// ...
receive(nextElement) {
// ...
}
}
class CompositeComponent {
// ...
receive(nextElement) {
var prevProps = this.currentElement.props;
var publicInstance = this.publicInstance;
var prevRenderedComponent = this.renderedComponent;
var prevRenderedElement = prevRenderedComponent.currentElement;
// Update *own* element
this.currentElement = nextElement;
var type = nextElement.type;
var nextProps = nextElement.props;
// Figure out what the next render() output is
var nextRenderedElement;
if (isClass(type)) {
// Component class
// Call the lifecycle if necessary
if (publicInstance.componentWillUpdate) {
publicInstance.componentWillUpdate(nextProps);
}
// Update the props
publicInstance.props = nextProps;
// Re-render
nextRenderedElement = publicInstance.render();
} else if (typeof type === "function") {
// Component function
nextRenderedElement = type(nextProps);
}
// ...
// ...
// 如果被渲染元素類型沒(méi)有被改變,
// 重用現(xiàn)有的組件實(shí)例.
if (prevRenderedElement.type === nextRenderedElement.type) {
prevRenderedComponent.receive(nextRenderedElement);
return;
}
// ...
例如,這就是當(dāng)一個(gè)之前被渲染的元素之后又被渲染成一個(gè) 的過(guò)程: // ...
// If we reached this point, we need to unmount the previously
// mounted component, mount the new one, and swap their nodes.
// Find the old node because it will need to be replaced
var prevNode = prevRenderedComponent.getHostNode();
// Unmount the old child and mount a new child
prevRenderedComponent.unmount();
var nextRenderedComponent = instantiateComponent(nextRenderedElement);
var nextNode = nextRenderedComponent.mount();
// Replace the reference to the child
this.renderedComponent = nextRenderedComponent;
// Replace the old node with the new one
// Note: this is renderer-specific code and
// ideally should live outside of CompositeComponent:
prevNode.parentNode.replaceChild(nextNode, prevNode);
}
}
或者卸載它,并在其位置上掛一個(gè)新元素。
它的實(shí)現(xiàn)對(duì)兩個(gè)類都很簡(jiǎn)單:class CompositeComponent {
// ...
getHostNode() {
// 請(qǐng)求渲染的組件提供它(Ask the rendered component to provide it).
// 這將遞歸地向下鉆取任何組合(This will recursively drill down any composites).
return this.renderedComponent.getHostNode();
}
}
class DOMComponent {
// ...
getHostNode() {
return this.node;
}
}
更新主機(jī)組件(Updating Host Components)
class DOMComponent {
// ...
receive(nextElement) {
var node = this.node;
var prevElement = this.currentElement;
var prevProps = prevElement.props;
var nextProps = nextElement.props;
this.currentElement = nextElement;
// Remove old attributes.
Object.keys(prevProps).forEach(propName => {
if (propName !== "children" && !nextProps.hasOwnProperty(propName)) {
node.removeAttribute(propName);
}
});
// Set next attributes.
Object.keys(nextProps).forEach(propName => {
if (propName !== "children") {
node.setAttribute(propName, nextProps[propName]);
}
});
// ...
真正的調(diào)解器(reconciler)同時(shí)在帳戶中獲取元素的key并且追蹤變動(dòng),除了插入與刪除,但是我們現(xiàn)在先忽略這一邏輯。 // ...
// // 這些是React元素(element)數(shù)組:
var prevChildren = prevProps.children || [];
if (!Array.isArray(prevChildren)) {
prevChildren = [prevChildren];
}
var nextChildren = nextProps.children || [];
if (!Array.isArray(nextChildren)) {
nextChildren = [nextChildren];
}
// 這些是內(nèi)部實(shí)例(internal instances)數(shù)組:
var prevRenderedChildren = this.renderedChildren;
var nextRenderedChildren = [];
// 當(dāng)我們遍歷children時(shí),我們將向數(shù)組中添加操作。
var operationQueue = [];
// 注意:以下章節(jié)大大減化!
// 它不處理reorders,空children,或者keys。
// 它只是用來(lái)解釋整個(gè)流程,而不是具體的細(xì)節(jié)。
for (var i = 0; i < nextChildren.length; i++) {
// 嘗試為這個(gè)子級(jí)獲取現(xiàn)存內(nèi)部實(shí)例。
var prevChild = prevRenderedChildren[i];
// 如果在這個(gè)索引下沒(méi)有內(nèi)部實(shí)例,那說(shuō)明是一個(gè)child被添加了末尾。
// 這時(shí)應(yīng)該去創(chuàng)建一個(gè)內(nèi)部實(shí)例,掛載它,并使用它的節(jié)點(diǎn)。
if (!prevChild) {
var nextChild = instantiateComponent(nextChildren[i]);
var node = nextChild.mount();
// 記錄一下我們將來(lái)需要append一個(gè)節(jié)點(diǎn)(node)
operationQueue.push({type: "ADD", node});
nextRenderedChildren.push(nextChild);
continue;
}
// 如果它的元素類型匹配,我們只需要更新該實(shí)例即可
// 例如, 可以更新為
// 但是不能被更新為
// ...
// 處理隊(duì)列里的operation。
while (operationQueue.length > 0) {
var operation = operationQueue.shift();
switch (operation.type) {
case "ADD":
this.node.appendChild(operation.node);
break;
case "REPLACE":
this.node.replaceChild(operation.nextNode, operation.prevNode);
break;
case "REMOVE":
this.node.removeChild(operation.node);
break;
}
}
}
}
我們現(xiàn)在可以改變頂級(jí) mountTree() 函數(shù)了,當(dāng)元素(element)的type相同時(shí),我們可以使用receive了。function mountTree(element, containerNode) {
// Check for an existing tree
if (containerNode.firstChild) {
var prevNode = containerNode.firstChild;
var prevRootComponent = prevNode._internalInstance;
var prevElement = prevRootComponent.currentElement;
// 如果可以,使用現(xiàn)存根組件
if (prevElement.type === element.type) {
prevRootComponent.receive(element);
return;
}
// 否則,卸載現(xiàn)存樹(shù)
unmountTree(containerNode);
}
// ...
}
var rootEl = document.getElementById("root");
mountTree(
事務(wù)還可以確保在更新后“清除所有內(nèi)容”。例如,由 React DOM提供的事務(wù)類在任何更新之后恢復(fù)input的選中與否。
ReactNativeMount is its React Native analog.
ReactNativeBaseComponent is its React Native analog.
包含選擇正確的內(nèi)部實(shí)例類并運(yùn)行element的構(gòu)造函數(shù)。在本教程中,它與instantiateComponent()等同。
它調(diào)用內(nèi)部實(shí)例的底層實(shí)現(xiàn),但也包含了所有內(nèi)部實(shí)例實(shí)現(xiàn)共享的代碼。
我們正在實(shí)現(xiàn)一個(gè)新的協(xié)調(diào)器Fiber reconciler,
你可以在這里看它的具體思路
將來(lái)我們會(huì)用fiber協(xié)調(diào)器代替stack協(xié)調(diào)器(譯者注:其實(shí)現(xiàn)在react16已經(jīng)發(fā)布,在react16中fiber算法已經(jīng)取代了stack算法)相關(guān)文章
淺談React Fiber
react源碼總覽(翻譯)
漫談前端性能 突破 React 應(yīng)用瓶頸
React Fiber 原理介紹
發(fā)表評(píng)論
0條評(píng)論
閱讀 785·2021-11-16 11:44
閱讀 3637·2019-08-26 12:13
閱讀 3291·2019-08-26 10:46
閱讀 2418·2019-08-23 12:37
閱讀 1260·2019-08-22 18:30
閱讀 2598·2019-08-22 17:30
閱讀 1905·2019-08-22 17:26
閱讀 2353·2019-08-22 16:20