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