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