亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

React diff算法的實現示例

2024-05-06 16:43:49
字體:
來源:轉載
供稿:網友

前言

在上一篇文章,我們已經實現了React的組件功能,從功能的角度來說已經實現了React的核心功能了。

但是我們的實現方式有很大的問題:每次更新都重新渲染整個應用或者整個組件,DOM操作十分昂貴,這樣性能損耗非常大。

為了減少DOM更新,我們需要找渲染前后真正變化的部分,只更新這一部分DOM。而對比變化,找出需要更新部分的算法我們稱之為diff算法。

對比策略

在前面兩篇文章后,我們實現了一個render方法,它能將虛擬DOM渲染成真正的DOM,我們現在就需要改進它,讓它不要再傻乎乎地重新渲染整個DOM樹,而是找出真正變化的部分。

這部分很多類React框架實現方式都不太一樣,有的框架會選擇保存上次渲染的虛擬DOM,然后對比虛擬DOM前后的變化,得到一系列更新的數據,然后再將這些更新應用到真正的DOM上。

但也有一些框架會選擇直接對比虛擬DOM和真實DOM,這樣就不需要額外保存上一次渲染的虛擬DOM,并且能夠一邊對比一邊更新,這也是我們選擇的方式。

不管是DOM還是虛擬DOM,它們的結構都是一棵樹,完全對比兩棵樹變化的算法時間復雜度是O(n^3),但是考慮到我們很少會跨層級移動DOM,所以我們只需要對比同一層級的變化。

React,diff算法

只需要對比同一顏色框內的節點

總而言之,我們的diff算法有兩個原則:

  1. 對比當前真實的DOM和虛擬DOM,在對比過程中直接更新真實DOM
  2. 只對比同一層級的變化實現

我們需要實現一個diff方法,它的作用是對比真實DOM和虛擬DOM,最后返回更新后的DOM

/** * @param {HTMLElement} dom 真實DOM * @param {vnode} vnode 虛擬DOM * @returns {HTMLElement} 更新后的DOM */function diff( dom, vnode ) {  // ...}

接下來就要實現這個方法。

在這之前先來回憶一下我們虛擬DOM的結構:

虛擬DOM的結構可以分為三種,分別表示文本、原生DOM節點以及組件。

// 原生DOM節點的vnode{  tag: 'div',  attrs: {    className: 'container'  },  children: []}// 文本節點的vnode"hello,world"// 組件的vnode{  tag: ComponentConstrucotr,  attrs: {    className: 'container'  },  children: []}

對比文本節點

首先考慮最簡單的文本節點,如果當前的DOM就是文本節點,則直接更新內容,否則就新建一個文本節點,并移除掉原來的DOM。

// diff text nodeif ( typeof vnode === 'string' ) {  // 如果當前的DOM就是文本節點,則直接更新內容  if ( dom && dom.nodeType === 3 ) {  // nodeType: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType    if ( dom.textContent !== vnode ) {      dom.textContent = vnode;    }  // 如果DOM不是文本節點,則新建一個文本節點DOM,并移除掉原來的  } else {    out = document.createTextNode( vnode );    if ( dom && dom.parentNode ) {      dom.parentNode.replaceChild( out, dom );    }  }  return out;}

文本節點十分簡單,它沒有屬性,也沒有子元素,所以這一步結束后就可以直接返回結果了。

對比非文本DOM節點

如果vnode表示的是一個非文本的DOM節點,那就要分幾種情況了:

如果真實DOM和虛擬DOM的類型不同,例如當前真實DOM是一個div,而vnode的tag的值是'button',那么原來的div就沒有利用價值了,直接新建一個button元素,并將div的所有子節點移到button下,然后用replaceChild方法將div替換成button。

if ( !dom || dom.nodeName.toLowerCase() !== vnode.tag.toLowerCase() ) {  out = document.createElement( vnode.tag );  if ( dom ) {    [ ...dom.childNodes ].map( out.appendChild );  // 將原來的子節點移到新節點下    if ( dom.parentNode ) {      dom.parentNode.replaceChild( out, dom );  // 移除掉原來的DOM對象    }  }}

如果真實DOM和虛擬DOM是同一類型的,那我們暫時不需要做別的,只需要等待后面對比屬性和對比子節點。

對比屬性

實際上diff算法不僅僅是找出節點類型的變化,它還要找出來節點的屬性以及事件監聽的變化。我們將對比屬性單獨拿出來作為一個方法:

function diffAttributes( dom, vnode ) {  const old = dom.attributes;  // 當前DOM的屬性  const attrs = vnode.attrs;   // 虛擬DOM的屬性  // 如果原來的屬性不在新的屬性當中,則將其移除掉(屬性值設為undefined)  for ( let name in old ) {    if ( !( name in attrs ) ) {      setAttribute( dom, name, undefined );    }  }  // 更新新的屬性值  for ( let name in attrs ) {    if ( old[ name ] !== attrs[ name ] ) {      setAttribute( dom, name, attrs[ name ] );    }  }}

setAttribute方法的實現參見第一篇文章

對比子節點

節點本身對比完成了,接下來就是對比它的子節點。

這里會面臨一個問題,前面我們實現的不同diff方法,都是明確知道哪一個真實DOM和虛擬DOM對比,但是子節點是一個數組,它們可能改變了順序,或者數量有所變化,我們很難確定要和虛擬DOM對比的是哪一個。

為了簡化邏輯,我們可以讓用戶提供一些線索:給節點設一個key值,重新渲染時對比key值相同的節點。

// diff方法if ( vnode.children && vnode.children.length > 0 || ( out.childNodes && out.childNodes.length > 0 ) ) {  diffChildren( out, vnode.children );}
function diffChildren( dom, vchildren ) {  const domChildren = dom.childNodes;  const children = [];  const keyed = {};  // 將有key的節點和沒有key的節點分開  if ( domChildren.length > 0 ) {    for ( let i = 0; i < domChildren.length; i++ ) {      const child = domChildren[ i ];      const key = child.key;      if ( key ) {        keyedLen++;        keyed[ key ] = child;      } else {        children.push( child );      }    }  }  if ( vchildren && vchildren.length > 0 ) {    let min = 0;    let childrenLen = children.length;    for ( let i = 0; i < vchildren.length; i++ ) {      const vchild = vchildren[ i ];      const key = vchild.key;      let child;      // 如果有key,找到對應key值的節點      if ( key ) {        if ( keyed[ key ] ) {          child = keyed[ key ];          keyed[ key ] = undefined;        }      // 如果沒有key,則優先找類型相同的節點      } else if ( min < childrenLen ) {        for ( let j = min; j < childrenLen; j++ ) {          let c = children[ j ];          if ( c && isSameNodeType( c, vchild ) ) {            child = c;            children[ j ] = undefined;            if ( j === childrenLen - 1 ) childrenLen--;            if ( j === min ) min++;            break;          }        }      }      // 對比      child = diff( child, vchild );      // 更新DOM      const f = domChildren[ i ];      if ( child && child !== dom && child !== f ) {        if ( !f ) {          dom.appendChild(child);        } else if ( child === f.nextSibling ) {          removeNode( f );        } else {          dom.insertBefore( child, f );        }      }    }  }}

對比組件

如果vnode是一個組件,我們也單獨拿出來作為一個方法:

function diffComponent( dom, vnode ) {  let c = dom && dom._component;  let oldDom = dom;  // 如果組件類型沒有變化,則重新set props  if ( c && c.constructor === vnode.tag ) {    setComponentProps( c, vnode.attrs );    dom = c.base;  // 如果組件類型變化,則移除掉原來組件,并渲染新的組件  } else {    if ( c ) {      unmountComponent( c );      oldDom = null;    }    c = createComponent( vnode.tag, vnode.attrs );    setComponentProps( c, vnode.attrs );    dom = c.base;    if ( oldDom && dom !== oldDom ) {      oldDom._component = null;      removeNode( oldDom );    }  }  return dom;}

下面是相關的工具方法的實現,和上一篇文章的實現相比,只需要修改renderComponent方法其中的一行。

function renderComponent( component ) {    // ...  // base = base = _render( renderer );     // 將_render改成diff  base = diff( component.base, renderer );  // ...}

完整diff實現看這個文件

渲染

現在我們實現了diff方法,我們嘗試渲染上一篇文章中定義的Counter組件,來感受一下有無diff方法的不同。

class Counter extends React.Component {  constructor( props ) {    super( props );    this.state = {      num: 1    }  }  onClick() {    this.setState( { num: this.state.num + 1 } );  }  render() {    return (      <div>        <h1>count: { this.state.num }</h1>        <button onClick={ () => this.onClick()}>add</button>      </div>    );  }}

不使用diff

使用上一篇文章的實現,從chrome的調試工具中可以看到,閃爍的部分是每次更新的部分,每次點擊按鈕,都會重新渲染整個組件。

React,diff算法

使用diff

而實現了diff方法后,每次點擊按鈕,都只會重新渲染變化的部分。

React,diff算法

后話

在這篇文章中我們實現了diff算法,通過它做到了每次只更新需要更新的部分,極大地減少了DOM操作。React實現遠比這個要復雜,特別是在React 16之后還引入了Fiber架構,但是主要的思想是一致的。

實現diff算法可以說性能有了很大的提升,但是在別的地方仍然后很多改進的空間:每次調用setState后會立即調用renderComponent重新渲染組件,但現實情況是,我們可能會在極短的時間內多次調用setState。

假設我們在上文的Counter組件中寫出了這種代碼

onClick() {  for ( let i = 0; i < 100; i++ ) {    this.setState( { num: this.state.num + 1 } );  }}

那以目前的實現,每次點擊都會渲染100次組件,對性能肯定有很大的影響。

下一篇文章我們就要來改進setState方法

這篇文章的代碼:https://github.com/hujiulong/simple-react/tree/chapter-3

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久这里有精品视频| 亚洲视频欧洲视频| 日韩欧美国产高清91| 久久精品91久久久久久再现| 亚洲日本aⅴ片在线观看香蕉| 日韩电影在线观看中文字幕| 国产专区精品视频| 免费av在线一区| 亚洲精品中文字幕女同| 亚洲综合成人婷婷小说| 成人精品一区二区三区电影黑人| 国产精品揄拍500视频| 免费91麻豆精品国产自产在线观看| 一本一道久久a久久精品逆3p| 欧美怡红院视频一区二区三区| 精品偷拍各种wc美女嘘嘘| 亚洲a级在线播放观看| 成人xxxxx| 亚洲一区二区三区乱码aⅴ蜜桃女| 欧美综合激情网| 57pao成人国产永久免费| 亚洲xxxx18| 91在线网站视频| 久久激情视频免费观看| 欧洲美女免费图片一区| 亚洲v日韩v综合v精品v| 国产精品视频在线播放| 亚洲qvod图片区电影| 精品国产自在精品国产浪潮| 亚洲色图第三页| 国产成人精品久久二区二区| 欧美性videos高清精品| 97精品视频在线播放| 久久精品久久久久| 色婷婷亚洲mv天堂mv在影片| 国产欧美日韩专区发布| 国产精品福利网站| 亚洲а∨天堂久久精品喷水| 欧美精品一二区| 国产在线拍揄自揄视频不卡99| 日韩高清电影好看的电视剧电影| 中文字幕一区二区三区电影| 久久久亚洲国产| 欧美日韩午夜激情| 国产一区二区激情| 欧美美女操人视频| 午夜精品蜜臀一区二区三区免费| 日韩av免费网站| 国产精品一区二区三区久久| 久久全国免费视频| 久久综合网hezyo| 波霸ol色综合久久| 欧美激情精品久久久久| 91免费国产网站| 精品久久久av| 国产日韩欧美综合| 最新国产精品亚洲| 国产suv精品一区二区| 精品国产一区二区三区久久久狼| 亚洲国产精品va在看黑人| 欧美性xxxxhd| 色妞欧美日韩在线| 97在线视频观看| 亚洲精品色婷婷福利天堂| 日韩成人在线免费观看| 中文字幕九色91在线| 成人xvideos免费视频| 韩国国内大量揄拍精品视频| 色综合久久悠悠| 久久免费视频这里只有精品| 91美女福利视频高清| 成人精品在线观看| 国产免费一区视频观看免费| 国内精品久久久久影院优| 免费97视频在线精品国自产拍| 国产91精品最新在线播放| 亚洲福利视频专区| 尤物yw午夜国产精品视频明星| 国产日韩精品视频| 免费97视频在线精品国自产拍| 爽爽爽爽爽爽爽成人免费观看| 亚洲精品久久7777777| 欧美日韩国产中文字幕| 91夜夜未满十八勿入爽爽影院| 国产精品自产拍在线观| 国产91免费看片| 久久久久久久久久久国产| 欧美性猛交视频| 美女性感视频久久久| 97热在线精品视频在线观看| 97视频人免费观看| 日本不卡视频在线播放| 亚洲视频欧美视频| 亚洲最大福利网| 亚洲香蕉伊综合在人在线视看| 日韩精品在线视频| 精品丝袜一区二区三区| 欧美日韩在线免费观看| 久热国产精品视频| 国产日韩综合一区二区性色av| 国产视频精品久久久| 欧美国产视频一区二区| 欧美一级成年大片在线观看| 日韩成人在线免费观看| 亚洲人午夜精品| 中文字幕一区二区三区电影| 欧美在线免费视频| 亚洲精品白浆高清久久久久久| 欧美精品情趣视频| 91免费电影网站| 国产精品专区第二| 91欧美精品午夜性色福利在线| 亚洲色图50p| 超碰精品一区二区三区乱码| 久久精品视频99| www.日韩av.com| 欧美日韩亚洲系列| 波霸ol色综合久久| 日韩精品免费一线在线观看| 国产精品18久久久久久首页狼| 一区二区三区动漫| 日韩在线视频网| 欧美高跟鞋交xxxxxhd| 久久国产精品偷| 久久中文字幕在线视频| 久久久人成影片一区二区三区观看| 成人av在线天堂| 中文综合在线观看| 欧美日韩在线看| 欧美日韩国产精品专区| 亚洲va欧美va国产综合剧情| 5278欧美一区二区三区| 久久亚洲精品国产亚洲老地址| 亚洲美女av黄| 色偷偷综合社区| 国产一区二区三区在线看| 色悠悠久久久久| 亚洲国产一区二区三区四区| 国产91久久婷婷一区二区| 久久久日本电影| 日韩av手机在线观看| 国产精品入口免费视| 欧美大学生性色视频| 欧美最猛性xxxxx免费| 欧美大片在线免费观看| 人体精品一二三区| 亚洲天堂免费视频| 中文字幕v亚洲ⅴv天堂| 国产精品扒开腿做爽爽爽视频| 日韩av手机在线观看| 成人h猎奇视频网站| 91欧美日韩一区| 欧美在线激情网| 亚洲福利精品在线| 中文字幕无线精品亚洲乱码一区| 国语自产精品视频在线看抢先版图片| 韩国国内大量揄拍精品视频| 91亚洲永久免费精品| 国语自产精品视频在线看一大j8| 日韩中文字幕视频| 狠狠色狠狠色综合日日小说| 亚洲自拍偷拍区| 日韩人体视频一二区| 97免费视频在线|