摘要:翻譯自截止目前我們已經(jīng)可以使用來創(chuàng)建并渲染頁面。是由子元素對應(yīng)實例組成的數(shù)組。出現(xiàn)這個錯誤是因為我們沒有考慮節(jié)點需要移除的情況。注意這地方返回了這一節(jié),我們?yōu)樵黾恿烁碌墓δ?。我們通過重用節(jié)點,避免了頻繁的創(chuàng)建和移除節(jié)點,提高了的工作效率。
翻譯自:https://engineering.hexacta.c...
截止目前我們已經(jīng)可以使用JSX來創(chuàng)建并渲染頁面DOM。在這一節(jié)我們將會把重點放在如何更新DOM上。
在介紹setState之前,更新DOM只能通過更改入?yún)⒉⒃俅握{(diào)用render方法來實現(xiàn)。如果我們想實現(xiàn)一個時鐘,代碼大概下面這個樣子:
const rootDom = document.getElementById("root"); function tick() { const time = new Date().toLocaleTimeString(); const clockElement ={time}
; render(clockElement, rootDom); } tick(); setInterval(tick, 1000);
事實上,上面的代碼運行后并不能達到預(yù)期的效果,多次調(diào)用當(dāng)前版本的render方法只會不斷往頁面上添加新的元素,而不是我們預(yù)期的更新已經(jīng)存在的元素。下面我們想辦法實現(xiàn)更新操作。在render方法末尾,我們可以去檢查父類元素是否含有子元素,如果有,我們就用新生成的元素去替換舊的元素。
function render(element, parentDom){ // ... // Create dom from element // ... if(!parentDom.lastChild){ parentDom.appendChild(dom); } else { parentDom.replaceChild(dom, parentDom.lastChild); } }
針對開頭那個時鐘的例子,上面render的實現(xiàn)是沒問題的。但對于更復(fù)雜的情況,比如有多個子元素時上面代碼就不能滿足要求了。正確的做法是我們需要比較前后兩次調(diào)用render方法時所生成的元素樹,對比差異后只更新有變化的部分。
Virtual DOM and ReconciliationReact把一致性校驗的過程稱作“diffing”,我們要做的和React一樣。首先需要把當(dāng)前的元素樹保存起來以便和后面新的元素樹比較,也就是說,我們需要把當(dāng)前頁面內(nèi)容所對應(yīng)的虛擬DOM保存下來。
這顆虛擬DOM樹的節(jié)點有必要討論一下。一種選擇是使用Didact Elements,它們已經(jīng)含有props.children屬性,我們可以根據(jù)這個屬性構(gòu)建出虛擬DOM樹?,F(xiàn)在有兩個問題擺在面前:首先,為了方便比較,我們需要保存每個虛擬DOM指向的真實DOM的引用(校驗過程中我們有需要會去更新實際DOM的屬性),并且元素還要是不可變的;第二,目前元素還不支持含有內(nèi)部狀態(tài)(state)的組件。
Instances我們需要引入一個新的概念-----instances-----來解決上面的問題。一個實例表示一個已經(jīng)渲染到DOM的元素,它是含有element,dom和childInstances屬性的一個JS對象。childInstances是由子元素對應(yīng)實例組成的數(shù)組。
注意,這里說的實例和Dan Abramov在React Components, Elements, and Instances中提到的實例并不是一回事。Dan說的是公共實例,是調(diào)用繼承自React.Component的組件的構(gòu)造函數(shù)后返回的東西。我們將在后面的章節(jié)添加公共實例。
每個DOM節(jié)點都會有對應(yīng)的實例。一致性校驗的目的之一就是盡量避免去創(chuàng)建或者移除實例。創(chuàng)建和移除實例意味著我們要修改DOM樹,所以我們越多的重用實例就會越少的去修改DOM樹。
Refactoring接下來我們來重寫render方法,增加一致性校驗算法,同時增加一個instantiate方法來為元素創(chuàng)建實例。
let rootInstance = null; // 用來保存上一次調(diào)用render產(chǎn)生的實例 function render(element, container){ const prevInstance = rootInstance; const nextInstance = reconcile(container, prevInstance, element); rootInstance = nextInstace; } // 目前只是針對根元素的校驗,沒有處理到子元素 function reconcile(parentDom, instance, element){ if(instance === null){ const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return newInstance; } else { const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; } } // 生成元素對應(yīng)實例的方法 function instantiate(element){ const { type, props} = element; const isTextElement = type === "TEXT_ELEMENT"; const dom = isTextElement ? document.createTextNode("") : document.createElement(type); // 添加事件 const isListener = name => name.startsWith("on"); Object.keys(props).filter(isListener).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, props[name]); }); // 設(shè)置屬性 const isAttribute = name => !isListener(name) && name != "children"; Object.keys(props).filter(isAttribute).forEach(name => { dom[name] = props[name]; }); const childElements = props.children || []; const childInstances = childElements.map(instantiate); const childDoms = childInstances.map(childInstance => childInstace.dom); childDoms.forEach(childDom => dom.appendChild(childDOm)); const instance = {dom, element, childInstances}; return instance; }
上面的render方法和之前的差不多,不同之處是保存了上次調(diào)用render方法產(chǎn)生的實例。我們還把一致性校驗的功能從創(chuàng)建實例的代碼中分離了出來。
為了重用dom節(jié)點,我們需要一個能更新dom屬性的方法,這樣就不用每次都創(chuàng)建新的dom節(jié)點了。我們來改造一下現(xiàn)有代碼中設(shè)置屬性的那部分的代碼。
function instantiate(element) { const { type, props } = element; // 創(chuàng)建DOM元素 const isTextElement = type === "TEXT_ELEMENT"; const dom = isTextElement ? document.createTextNode("") : document.createElement(type); updateDomProperties(dom, [], props); // 實例化一個新的元素 // 實例化并添加子元素 const childElements = props.children || []; const childInstances = childElements.map(instantiate); const childDoms = childInstances.map(childInstance => childInstance.dom); childDoms.forEach(childDom => dom.appendChild(childDom)); const instance = { dom, element, childInstances }; return instance; } function updateDomProperties(dom, prevProps, nextProps){ const isEvent = name => name.startsWith("on"); const isAttribute = name => !isEvent(name) && name != "children"; Object.keys(prevProps).filter(isEvent).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.removeEventListener(eventType, prevProps[name]); }); Object.keys(preProps).filter(isAttribute).forEach(name => { dom[name] = nextProps[name]; }); // 設(shè)置屬性 Object.keys(nextProps).filter(isAttribute).forEach(name => { dom[name] = nextProps[name]; }); // 添加事件監(jiān)聽 Object.keys(nextProps).filter(isEvent).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, nextProps[name]); }); }
updateDomProperties方法會移除所有舊的屬性,然后再添加新屬性。如果屬性沒有變化的話依然會進行移除和添加操作,這一定程度上有些浪費,但我們先這樣放著,后面再處理。
Reusing DOM nodes前面說過,一致性校驗算法需要盡可能多的去重用已經(jīng)創(chuàng)建的節(jié)點。因為目前元素的type都是代表HTML中標(biāo)簽名的字符串,所以如果同一位置前后兩次渲染的元素的類型一樣則表示兩者為同一類元素,對應(yīng)的已經(jīng)渲染到頁面上的dom節(jié)點就可以被重用。下面我們在reconcile中增加判斷前后兩次渲染的元素類型是否相同的功能,相同的話執(zhí)行更新操作,否則是新建或者替換。
function reconcile(parentDom, instance, element) { if (instance == null) { // 創(chuàng)建實例 const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return newInstance; } else if (instance.element.type === element.type) { // 和老的實例進行類型比較 // 更新 updateDomProperties(instance.dom, instance.element.props, element.props); instance.element = element; return instance; } else { // 如果不相等的話直接替換 const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; } }Children Reconciliation
現(xiàn)在校驗過程還沒有對子元素進行處理。針對子元素的校驗是React中的一個關(guān)鍵部分,這一過程需要元素的一個額外屬性key來完成,如果某個元素在新舊虛擬DOM上的key值相同,則表示該元素沒有發(fā)生變化,直接重用即可。在當(dāng)前版本的代碼中我們會遍歷instance.childInstances和element.props.children,并對同一位置的實例和元素進行比較,通過這種方式完成對子元素的一致性校驗。這種方法的缺點就是,如果子元素只是調(diào)換了位置,那么對應(yīng)的DOM節(jié)點將沒法重用。
我們把同一實例上一次的instance.childInstances和這次對應(yīng)元素的element.props.children進行遞歸比較,并且保存每次reconcile返回的結(jié)果以便更新childInstances。
function reconcile(parentDom, instance, element){ if(instance == null){ const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return newInstance; } else if(instance.element.type === element.type){ updateDomProperties(instance.dom, instance.element.props, element.props); instance.childInstances = reconcileChildren(instance, element); instance.element = element; return instance; } else { const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; } } function reconcileChildren(instance, element){ const dom = instance.dom; const childInstances = instance.childInstances; const nextChildElements = element.props.children || []; const newChildInstances = []; const count = Math.max(childInstances.length, nextChildElements.length); for(let i = 0; i< count; i++){ const childInstance = childInstances[i]; const childElement = nextChildElements[i];//上面一行和這一行都容易出現(xiàn)空指針,稍后處理 const newChildInstance = reconcile(dom, childInstance, childElement); newChildInstances.push(newChildInstance); } return newChildInstances; }Removing DOM nodes
如果nextChildElements數(shù)量多于childInstances,那么對子元素進行一致性校驗時就容易出現(xiàn)undefined與剩下的子元素進行比較的情況。不過這不是什么大問題,因為在reconcile中的if(instance == null)會處理這種情況,并且會根據(jù)多出來的元素創(chuàng)建新的實例。如果childInstances的數(shù)量多于nextChildElement,那么reconcile就會收到一個undefined作為其element參數(shù),然后在嘗試獲取element.type時就會拋出錯誤。
出現(xiàn)這個錯誤是因為我們沒有考慮DOM節(jié)點需要移除的情況。所以接下來我們要做兩件事情,一個是在reconcile中增加增加element === null的校驗,一個是在reconcileChildren中過濾掉值為null的childInstances元素。
function reconcile(parentDom, instance, element){ if(instance == null){ const newInstance = instantiate(element); parentDom.appendChild(newInstance.dom); return Instance; } else if(element == null){ parentDom.removeChild(instance.dom); return null; // 注意這地方返回null了 } else if(instance.element.type === element.type){ updateDomProperties(instance.dom, instance.element.props, element.props); instance.childInstances = reconcileChildren(instance, element); instance.element = element; return instance; } else { const newInstance = instantiate(element); parentDom.replaceChild(newInstance.dom, instance.dom); return newInstance; } } function reconcileChildren(instance, element){ const dom = instance.dom; const childInstances = instance.childInstances; const nextChildElements = element.props.children || []; const newChildInstances = []; const count = Math.max(childInstances.length, nextChildElements.length); for(let i = 0; i < count; i++){ const childInstance = childInstances[i]; const childElement = nextChildElements[i]; const newChildInstances = reconcile(dom, childInstance, childElement); newChildInstances.push(newChildInstance); } return newChildInstances.filter(instance => instance != null) }Summary
這一節(jié),我們?yōu)镈idact增加了更新DOM的功能。我們通過重用節(jié)點,避免了頻繁的創(chuàng)建和移除DOM節(jié)點,提高了Didact的工作效率。重用節(jié)點還有一定的好處,比如保存了DOM的位置或者焦點等一些內(nèi)部狀態(tài)信息。
目前我們是在根元素上調(diào)用render方法的,每次有變化時也是針對整棵元素樹進行的一致性校驗。下一節(jié)我們將介紹組件。有了組件我們就可以只針對有變化的那一部分子樹進行一致性校驗。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/99871.html
摘要:翻譯自截止目前我們已經(jīng)可以使用來創(chuàng)建并渲染頁面。是由子元素對應(yīng)實例組成的數(shù)組。出現(xiàn)這個錯誤是因為我們沒有考慮節(jié)點需要移除的情況。注意這地方返回了這一節(jié),我們?yōu)樵黾恿烁碌墓δ?。我們通過重用節(jié)點,避免了頻繁的創(chuàng)建和移除節(jié)點,提高了的工作效率。 翻譯自:https://engineering.hexacta.c... 截止目前我們已經(jīng)可以使用JSX來創(chuàng)建并渲染頁面DOM。在這一節(jié)我們將會把...
摘要:它的主體特征是增量渲染能夠?qū)秩竟ぷ鞣指畛蓧K,并將其分散到多個幀中。實際上,這樣做可能會造成浪費,導(dǎo)致幀丟失并降低用戶體驗。當(dāng)一個函數(shù)被執(zhí)行時,一個新的堆棧框架被添加到堆棧中。該堆棧框表示由該函數(shù)執(zhí)行的工作。 原文 react-fiber-architecture 介紹 React Fibre是React核心算法正在進行的重新實現(xiàn)。它是React團隊兩年多的研究成果。 React ...
摘要:事件行為在瀏覽器中保持一次,并且符合標(biāo)準(zhǔn)。主要是進行修復(fù)。事件已經(jīng)在移動上支持。阻止已經(jīng)在上存在的事件錯誤處理。然后對應(yīng)的將會被打包送往客戶端。在中棄用,現(xiàn)在正式刪除。是運行于一個嚴(yán)格的安全策略下成為可能。增加警告提示非生產(chǎn)環(huán)境。 ??寫在開頭 閱讀React官網(wǎng)的 RECENT POSTS的個人翻譯/摘要(部分)。 英文片段為官網(wǎng)原文片段。 原文地址 ??為什么要使用React ...
閱讀 3801·2021-11-12 10:34
閱讀 2817·2021-09-22 15:14
閱讀 788·2019-08-30 15:53
閱讀 3205·2019-08-30 12:53
閱讀 1288·2019-08-29 18:32
閱讀 2768·2019-08-29 16:41
閱讀 1065·2019-08-26 13:40
閱讀 1805·2019-08-23 18:07