亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

構(gòu)建自己的React:(3)Instances, reconciliation and virtua

KevinYan / 856人閱讀

摘要:翻譯自截止目前我們已經(jīng)可以使用來(lái)創(chuàng)建并渲染頁(yè)面。是由子元素對(duì)應(yīng)實(shí)例組成的數(shù)組。出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)槲覀儧](méi)有考慮節(jié)點(diǎn)需要移除的情況。注意這地方返回了這一節(jié),我們?yōu)樵黾恿烁碌墓δ?。我們通過(guò)重用節(jié)點(diǎn),避免了頻繁的創(chuàng)建和移除節(jié)點(diǎn),提高了的工作效率。

翻譯自:https://engineering.hexacta.c...

截止目前我們已經(jīng)可以使用JSX來(lái)創(chuàng)建并渲染頁(yè)面DOM。在這一節(jié)我們將會(huì)把重點(diǎn)放在如何更新DOM上。

在介紹setState之前,更新DOM只能通過(guò)更改入?yún)⒉⒃俅握{(diào)用render方法來(lái)實(shí)現(xiàn)。如果我們想實(shí)現(xiàn)一個(gè)時(shí)鐘,代碼大概下面這個(gè)樣子:

const rootDom = document.getElementById("root");

function tick() {
  const time = new Date().toLocaleTimeString();
  const clockElement = 

{time}

; render(clockElement, rootDom); } tick(); setInterval(tick, 1000);

事實(shí)上,上面的代碼運(yùn)行后并不能達(dá)到預(yù)期的效果,多次調(diào)用當(dāng)前版本的render方法只會(huì)不斷往頁(yè)面上添加新的元素,而不是我們預(yù)期的更新已經(jīng)存在的元素。下面我們想辦法實(shí)現(xiàn)更新操作。在render方法末尾,我們可以去檢查父類元素是否含有子元素,如果有,我們就用新生成的元素去替換舊的元素。

function render(element, parentDom){
    // ...
    // Create dom from element
    // ...
    if(!parentDom.lastChild){
        parentDom.appendChild(dom);
    } else {
        parentDom.replaceChild(dom, parentDom.lastChild);
    }
}

針對(duì)開(kāi)頭那個(gè)時(shí)鐘的例子,上面render的實(shí)現(xiàn)是沒(méi)問(wèn)題的。但對(duì)于更復(fù)雜的情況,比如有多個(gè)子元素時(shí)上面代碼就不能滿足要求了。正確的做法是我們需要比較前后兩次調(diào)用render方法時(shí)所生成的元素樹(shù),對(duì)比差異后只更新有變化的部分。

Virtual DOM and Reconciliation

React把一致性校驗(yàn)的過(guò)程稱作“diffing”,我們要做的和React一樣。首先需要把當(dāng)前的元素樹(shù)保存起來(lái)以便和后面新的元素樹(shù)比較,也就是說(shuō),我們需要把當(dāng)前頁(yè)面內(nèi)容所對(duì)應(yīng)的虛擬DOM保存下來(lái)。

這顆虛擬DOM樹(shù)的節(jié)點(diǎn)有必要討論一下。一種選擇是使用Didact Elements,它們已經(jīng)含有props.children屬性,我們可以根據(jù)這個(gè)屬性構(gòu)建出虛擬DOM樹(shù)?,F(xiàn)在有兩個(gè)問(wèn)題擺在面前:首先,為了方便比較,我們需要保存每個(gè)虛擬DOM指向的真實(shí)DOM的引用(校驗(yàn)過(guò)程中我們有需要會(huì)去更新實(shí)際DOM的屬性),并且元素還要是不可變的;第二,目前元素還不支持含有內(nèi)部狀態(tài)(state)的組件。

Instances

我們需要引入一個(gè)新的概念-----instances-----來(lái)解決上面的問(wèn)題。一個(gè)實(shí)例表示一個(gè)已經(jīng)渲染到DOM的元素,它是含有element,domchildInstances屬性的一個(gè)JS對(duì)象。childInstances是由子元素對(duì)應(yīng)實(shí)例組成的數(shù)組。

注意,這里說(shuō)的實(shí)例和Dan Abramov在React Components, Elements, and Instances中提到的實(shí)例并不是一回事。Dan說(shuō)的是公共實(shí)例,是調(diào)用繼承自React.Component的組件的構(gòu)造函數(shù)后返回的東西。我們將在后面的章節(jié)添加公共實(shí)例。

每個(gè)DOM節(jié)點(diǎn)都會(huì)有對(duì)應(yīng)的實(shí)例。一致性校驗(yàn)的目的之一就是盡量避免去創(chuàng)建或者移除實(shí)例。創(chuàng)建和移除實(shí)例意味著我們要修改DOM樹(shù),所以我們?cè)蕉嗟闹赜脤?shí)例就會(huì)越少的去修改DOM樹(shù)。

Refactoring

接下來(lái)我們來(lái)重寫(xiě)render方法,增加一致性校驗(yàn)算法,同時(shí)增加一個(gè)instantiate方法來(lái)為元素創(chuàng)建實(shí)例。

let rootInstance = null; // 用來(lái)保存上一次調(diào)用render產(chǎn)生的實(shí)例

function render(element, container){
    const prevInstance = rootInstance;
    const nextInstance = reconcile(container, prevInstance, element);
    rootInstance = nextInstace;
}

// 目前只是針對(duì)根元素的校驗(yàn),沒(méi)有處理到子元素
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;
    }
}

// 生成元素對(duì)應(yīng)實(shí)例的方法
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)生的實(shí)例。我們還把一致性校驗(yàn)的功能從創(chuàng)建實(shí)例的代碼中分離了出來(lái)。

為了重用dom節(jié)點(diǎn),我們需要一個(gè)能更新dom屬性的方法,這樣就不用每次都創(chuàng)建新的dom節(jié)點(diǎn)了。我們來(lái)改造一下現(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); // 實(shí)例化一個(gè)新的元素

  // 實(shí)例化并添加子元素
  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)聽(tīng)
      Object.keys(nextProps).filter(isEvent).forEach(name => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, nextProps[name]);
      });
}

updateDomProperties方法會(huì)移除所有舊的屬性,然后再添加新屬性。如果屬性沒(méi)有變化的話依然會(huì)進(jìn)行移除和添加操作,這一定程度上有些浪費(fèi),但我們先這樣放著,后面再處理。

Reusing DOM nodes

前面說(shuō)過(guò),一致性校驗(yàn)算法需要盡可能多的去重用已經(jīng)創(chuàng)建的節(jié)點(diǎn)。因?yàn)槟壳霸氐?b>type都是代表HTML中標(biāo)簽名的字符串,所以如果同一位置前后兩次渲染的元素的類型一樣則表示兩者為同一類元素,對(duì)應(yīng)的已經(jīng)渲染到頁(yè)面上的dom節(jié)點(diǎn)就可以被重用。下面我們?cè)?b>reconcile中增加判斷前后兩次渲染的元素類型是否相同的功能,相同的話執(zhí)行更新操作,否則是新建或者替換。

function reconcile(parentDom, instance, element) {
  if (instance == null) {
    // 創(chuàng)建實(shí)例
    const newInstance = instantiate(element);
    parentDom.appendChild(newInstance.dom);
    return newInstance;
  } else if (instance.element.type === element.type) { // 和老的實(shí)例進(jìn)行類型比較
    // 更新
    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)在校驗(yàn)過(guò)程還沒(méi)有對(duì)子元素進(jìn)行處理。針對(duì)子元素的校驗(yàn)是React中的一個(gè)關(guān)鍵部分,這一過(guò)程需要元素的一個(gè)額外屬性key來(lái)完成,如果某個(gè)元素在新舊虛擬DOM上的key值相同,則表示該元素沒(méi)有發(fā)生變化,直接重用即可。在當(dāng)前版本的代碼中我們會(huì)遍歷instance.childInstanceselement.props.children,并對(duì)同一位置的實(shí)例和元素進(jìn)行比較,通過(guò)這種方式完成對(duì)子元素的一致性校驗(yàn)。這種方法的缺點(diǎn)就是,如果子元素只是調(diào)換了位置,那么對(duì)應(yīng)的DOM節(jié)點(diǎn)將沒(méi)法重用。

我們把同一實(shí)例上一次的instance.childInstances和這次對(duì)應(yīng)元素的element.props.children進(jìn)行遞歸比較,并且保存每次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,那么對(duì)子元素進(jìn)行一致性校驗(yàn)時(shí)就容易出現(xiàn)undefined與剩下的子元素進(jìn)行比較的情況。不過(guò)這不是什么大問(wèn)題,因?yàn)樵?b>reconcile中的if(instance == null)會(huì)處理這種情況,并且會(huì)根據(jù)多出來(lái)的元素創(chuàng)建新的實(shí)例。如果childInstances的數(shù)量多于nextChildElement,那么reconcile就會(huì)收到一個(gè)undefined作為其element參數(shù),然后在嘗試獲取element.type時(shí)就會(huì)拋出錯(cuò)誤。

出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)槲覀儧](méi)有考慮DOM節(jié)點(diǎn)需要移除的情況。所以接下來(lái)我們要做兩件事情,一個(gè)是在reconcile中增加增加element === null的校驗(yàn),一個(gè)是在reconcileChildren中過(guò)濾掉值為nullchildInstances元素。

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的功能。我們通過(guò)重用節(jié)點(diǎn),避免了頻繁的創(chuàng)建和移除DOM節(jié)點(diǎn),提高了Didact的工作效率。重用節(jié)點(diǎn)還有一定的好處,比如保存了DOM的位置或者焦點(diǎn)等一些內(nèi)部狀態(tài)信息。

目前我們是在根元素上調(diào)用render方法的,每次有變化時(shí)也是針對(duì)整棵元素樹(shù)進(jìn)行的一致性校驗(yàn)。下一節(jié)我們將介紹組件。有了組件我們就可以只針對(duì)有變化的那一部分子樹(shù)進(jìn)行一致性校驗(yàn)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/54684.html

相關(guān)文章

  • 構(gòu)建自己React:(3Instances, reconciliation and virtua

    摘要:翻譯自截止目前我們已經(jīng)可以使用來(lái)創(chuàng)建并渲染頁(yè)面。是由子元素對(duì)應(yīng)實(shí)例組成的數(shù)組。出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)槲覀儧](méi)有考慮節(jié)點(diǎn)需要移除的情況。注意這地方返回了這一節(jié),我們?yōu)樵黾恿烁碌墓δ堋N覀兺ㄟ^(guò)重用節(jié)點(diǎn),避免了頻繁的創(chuàng)建和移除節(jié)點(diǎn),提高了的工作效率。 翻譯自:https://engineering.hexacta.c... 截止目前我們已經(jīng)可以使用JSX來(lái)創(chuàng)建并渲染頁(yè)面DOM。在這一節(jié)我們將會(huì)把...

    whinc 評(píng)論0 收藏0
  • React系列——React Fiber 架構(gòu)介紹資料匯總(翻譯+中文資料)

    摘要:它的主體特征是增量渲染能夠?qū)秩竟ぷ鞣指畛蓧K,并將其分散到多個(gè)幀中。實(shí)際上,這樣做可能會(huì)造成浪費(fèi),導(dǎo)致幀丟失并降低用戶體驗(yàn)。當(dāng)一個(gè)函數(shù)被執(zhí)行時(shí),一個(gè)新的堆??蚣鼙惶砑拥蕉褩V?。該堆??虮硎居稍摵瘮?shù)執(zhí)行的工作。 原文 react-fiber-architecture 介紹 React Fibre是React核心算法正在進(jìn)行的重新實(shí)現(xiàn)。它是React團(tuán)隊(duì)兩年多的研究成果。 React ...

    taohonghui 評(píng)論0 收藏0
  • React官網(wǎng)RECENT POSTS閱讀

    摘要:事件行為在瀏覽器中保持一次,并且符合標(biāo)準(zhǔn)。主要是進(jìn)行修復(fù)。事件已經(jīng)在移動(dòng)上支持。阻止已經(jīng)在上存在的事件錯(cuò)誤處理。然后對(duì)應(yīng)的將會(huì)被打包送往客戶端。在中棄用,現(xiàn)在正式刪除。是運(yùn)行于一個(gè)嚴(yán)格的安全策略下成為可能。增加警告提示非生產(chǎn)環(huán)境。 ??寫(xiě)在開(kāi)頭 閱讀React官網(wǎng)的 RECENT POSTS的個(gè)人翻譯/摘要(部分)。 英文片段為官網(wǎng)原文片段。 原文地址 ??為什么要使用React ...

    Sike 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<