最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React diff算法的實(shí)現(xiàn)示例

 更新時(shí)間:2018年04月20日 09:51:57   作者:hujiulong  
這篇文章主要介紹了React diff算法的實(shí)現(xiàn)示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

前言

在上一篇文章,我們已經(jīng)實(shí)現(xiàn)了React的組件功能,從功能的角度來(lái)說(shuō)已經(jīng)實(shí)現(xiàn)了React的核心功能了。

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

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

對(duì)比策略

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

這部分很多類React框架實(shí)現(xiàn)方式都不太一樣,有的框架會(huì)選擇保存上次渲染的虛擬DOM,然后對(duì)比虛擬DOM前后的變化,得到一系列更新的數(shù)據(jù),然后再將這些更新應(yīng)用到真正的DOM上。

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

不管是DOM還是虛擬DOM,它們的結(jié)構(gòu)都是一棵樹,完全對(duì)比兩棵樹變化的算法時(shí)間復(fù)雜度是O(n^3),但是考慮到我們很少會(huì)跨層級(jí)移動(dòng)DOM,所以我們只需要對(duì)比同一層級(jí)的變化。


只需要對(duì)比同一顏色框內(nèi)的節(jié)點(diǎn)

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

  1. 對(duì)比當(dāng)前真實(shí)的DOM和虛擬DOM,在對(duì)比過(guò)程中直接更新真實(shí)DOM
  2. 只對(duì)比同一層級(jí)的變化實(shí)現(xiàn)

我們需要實(shí)現(xiàn)一個(gè)diff方法,它的作用是對(duì)比真實(shí)DOM和虛擬DOM,最后返回更新后的DOM

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

接下來(lái)就要實(shí)現(xiàn)這個(gè)方法。

在這之前先來(lái)回憶一下我們虛擬DOM的結(jié)構(gòu):

虛擬DOM的結(jié)構(gòu)可以分為三種,分別表示文本、原生DOM節(jié)點(diǎn)以及組件。

// 原生DOM節(jié)點(diǎn)的vnode
{
  tag: 'div',
  attrs: {
    className: 'container'
  },
  children: []
}

// 文本節(jié)點(diǎn)的vnode
"hello,world"

// 組件的vnode
{
  tag: ComponentConstrucotr,
  attrs: {
    className: 'container'
  },
  children: []
}

對(duì)比文本節(jié)點(diǎn)

首先考慮最簡(jiǎn)單的文本節(jié)點(diǎn),如果當(dāng)前的DOM就是文本節(jié)點(diǎn),則直接更新內(nèi)容,否則就新建一個(gè)文本節(jié)點(diǎn),并移除掉原來(lái)的DOM。

// diff text node
if ( typeof vnode === 'string' ) {

  // 如果當(dāng)前的DOM就是文本節(jié)點(diǎn),則直接更新內(nèi)容
  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不是文本節(jié)點(diǎn),則新建一個(gè)文本節(jié)點(diǎn)DOM,并移除掉原來(lái)的
  } else {
    out = document.createTextNode( vnode );
    if ( dom && dom.parentNode ) {
      dom.parentNode.replaceChild( out, dom );
    }
  }

  return out;
}

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

對(duì)比非文本DOM節(jié)點(diǎn)

如果vnode表示的是一個(gè)非文本的DOM節(jié)點(diǎn),那就要分幾種情況了:

如果真實(shí)DOM和虛擬DOM的類型不同,例如當(dāng)前真實(shí)DOM是一個(gè)div,而vnode的tag的值是'button',那么原來(lái)的div就沒有利用價(jià)值了,直接新建一個(gè)button元素,并將div的所有子節(jié)點(diǎn)移到button下,然后用replaceChild方法將div替換成button。

if ( !dom || dom.nodeName.toLowerCase() !== vnode.tag.toLowerCase() ) {
  out = document.createElement( vnode.tag );

  if ( dom ) {
    [ ...dom.childNodes ].map( out.appendChild );  // 將原來(lái)的子節(jié)點(diǎn)移到新節(jié)點(diǎn)下

    if ( dom.parentNode ) {
      dom.parentNode.replaceChild( out, dom );  // 移除掉原來(lái)的DOM對(duì)象
    }
  }
}

如果真實(shí)DOM和虛擬DOM是同一類型的,那我們暫時(shí)不需要做別的,只需要等待后面對(duì)比屬性和對(duì)比子節(jié)點(diǎn)。

對(duì)比屬性

實(shí)際上diff算法不僅僅是找出節(jié)點(diǎn)類型的變化,它還要找出來(lái)節(jié)點(diǎn)的屬性以及事件監(jiān)聽的變化。我們將對(duì)比屬性單獨(dú)拿出來(lái)作為一個(gè)方法:

function diffAttributes( dom, vnode ) {

  const old = dom.attributes;  // 當(dāng)前DOM的屬性
  const attrs = vnode.attrs;   // 虛擬DOM的屬性

  // 如果原來(lái)的屬性不在新的屬性當(dāng)中,則將其移除掉(屬性值設(shè)為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方法的實(shí)現(xiàn)參見第一篇文章

對(duì)比子節(jié)點(diǎn)

節(jié)點(diǎn)本身對(duì)比完成了,接下來(lái)就是對(duì)比它的子節(jié)點(diǎn)。

這里會(huì)面臨一個(gè)問題,前面我們實(shí)現(xiàn)的不同diff方法,都是明確知道哪一個(gè)真實(shí)DOM和虛擬DOM對(duì)比,但是子節(jié)點(diǎn)是一個(gè)數(shù)組,它們可能改變了順序,或者數(shù)量有所變化,我們很難確定要和虛擬DOM對(duì)比的是哪一個(gè)。

為了簡(jiǎn)化邏輯,我們可以讓用戶提供一些線索:給節(jié)點(diǎn)設(shè)一個(gè)key值,重新渲染時(shí)對(duì)比key值相同的節(jié)點(diǎn)。

// 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的節(jié)點(diǎn)和沒有key的節(jié)點(diǎn)分開
  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,找到對(duì)應(yīng)key值的節(jié)點(diǎn)
      if ( key ) {

        if ( keyed[ key ] ) {
          child = keyed[ key ];
          keyed[ key ] = undefined;
        }

      // 如果沒有key,則優(yōu)先找類型相同的節(jié)點(diǎn)
      } 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;

          }

        }

      }

      // 對(duì)比
      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 );
        }
      }

    }
  }

}

對(duì)比組件

如果vnode是一個(gè)組件,我們也單獨(dú)拿出來(lái)作為一個(gè)方法:

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;
  // 如果組件類型變化,則移除掉原來(lái)組件,并渲染新的組件
  } 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;

}

下面是相關(guān)的工具方法的實(shí)現(xiàn),和上一篇文章的實(shí)現(xiàn)相比,只需要修改renderComponent方法其中的一行。

function renderComponent( component ) {
  
  // ...

  // base = base = _render( renderer );     // 將_render改成diff
  base = diff( component.base, renderer );

  // ...
}

完整diff實(shí)現(xiàn)看這個(gè)文件

渲染

現(xiàn)在我們實(shí)現(xiàn)了diff方法,我們嘗試渲染上一篇文章中定義的Counter組件,來(lái)感受一下有無(wú)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

使用上一篇文章的實(shí)現(xiàn),從chrome的調(diào)試工具中可以看到,閃爍的部分是每次更新的部分,每次點(diǎn)擊按鈕,都會(huì)重新渲染整個(gè)組件。

使用diff

而實(shí)現(xiàn)了diff方法后,每次點(diǎn)擊按鈕,都只會(huì)重新渲染變化的部分。

后話

在這篇文章中我們實(shí)現(xiàn)了diff算法,通過(guò)它做到了每次只更新需要更新的部分,極大地減少了DOM操作。React實(shí)現(xiàn)遠(yuǎn)比這個(gè)要復(fù)雜,特別是在React 16之后還引入了Fiber架構(gòu),但是主要的思想是一致的。

實(shí)現(xiàn)diff算法可以說(shuō)性能有了很大的提升,但是在別的地方仍然后很多改進(jìn)的空間:每次調(diào)用setState后會(huì)立即調(diào)用renderComponent重新渲染組件,但現(xiàn)實(shí)情況是,我們可能會(huì)在極短的時(shí)間內(nèi)多次調(diào)用setState。

假設(shè)我們?cè)谏衔牡腃ounter組件中寫出了這種代碼

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

那以目前的實(shí)現(xiàn),每次點(diǎn)擊都會(huì)渲染100次組件,對(duì)性能肯定有很大的影響。

下一篇文章我們就要來(lái)改進(jìn)setState方法

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • ChatGLM?集成LangChain工具詳解

    ChatGLM?集成LangChain工具詳解

    這篇文章主要為大家介紹了Svelte和React框架使用比較,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • react使用axios實(shí)現(xiàn)上傳下載功能

    react使用axios實(shí)現(xiàn)上傳下載功能

    這篇文章主要為大家詳細(xì)介紹了react使用axios實(shí)現(xiàn)上傳下載功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • react.js 獲取真實(shí)的DOM節(jié)點(diǎn)實(shí)例(必看)

    react.js 獲取真實(shí)的DOM節(jié)點(diǎn)實(shí)例(必看)

    下面小編就為大家?guī)?lái)一篇react.js 獲取真實(shí)的DOM節(jié)點(diǎn)實(shí)例(必看)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • React中實(shí)現(xiàn)動(dòng)畫的兩種方式及對(duì)比詳解

    React中實(shí)現(xiàn)動(dòng)畫的兩種方式及對(duì)比詳解

    在現(xiàn)代 Web 開發(fā)中,動(dòng)畫不僅是提升用戶體驗(yàn)的重要手段,更是增強(qiáng)用戶交互、引導(dǎo)用戶行為、提升界面美感的關(guān)鍵因素,本文將通過(guò)兩個(gè)例子,講解一下如何在 React 中使用 CSS transition 和 Framer Motion 實(shí)現(xiàn)動(dòng)畫,并對(duì)比它們的優(yōu)缺點(diǎn),需要的朋友可以參考下
    2025-07-07
  • 解決React報(bào)錯(cuò)The?tag?is?unrecognized?in?this?browser

    解決React報(bào)錯(cuò)The?tag?is?unrecognized?in?this?browser

    這篇文章主要為大家介紹了解決React報(bào)錯(cuò)The?tag?is?unrecognized?in?this?browser示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 如何在React中直接使用Redux

    如何在React中直接使用Redux

    這篇文章主要介紹了如何在React中直接使用Redux,目前redux在react中使用是最多的,所以我們需要將之前編寫的redux代碼,融入到react當(dāng)中去,本文給大家詳細(xì)講解,需要的朋友可以參考下
    2022-11-11
  • React實(shí)時(shí)預(yù)覽react-live源碼解析

    React實(shí)時(shí)預(yù)覽react-live源碼解析

    這篇文章主要為大家介紹了React實(shí)時(shí)預(yù)覽react-live源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 在React項(xiàng)目中使用Eslint代碼檢查工具及常見問題

    在React項(xiàng)目中使用Eslint代碼檢查工具及常見問題

    這篇文章主要介紹了在React項(xiàng)目中使用Eslint代碼檢查工具及常見問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-10-10
  • 詳解React如何使用??useReducer???高階鉤子來(lái)管理狀態(tài)

    詳解React如何使用??useReducer???高階鉤子來(lái)管理狀態(tài)

    useReducer是React中的一個(gè)鉤子,用于替代?useState來(lái)管理復(fù)雜的狀態(tài)邏輯,本文將詳細(xì)介紹如何在React中使用?useReducer高階鉤子來(lái)管理狀態(tài),感興趣的可以了解下
    2025-02-02
  • React?組件之props屬性的使用

    React?組件之props屬性的使用

    在React中,props是組件之間傳遞數(shù)據(jù)的一種方式,它是?React?組件的基礎(chǔ),用于將數(shù)據(jù)從父組件傳遞到子組件,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下
    2025-09-09

最新評(píng)論

徐闻县| 潼南县| 右玉县| 开鲁县| 沙河市| 青岛市| 来凤县| 海原县| 屯留县| 保德县| 四平市| 垣曲县| 万全县| 昌图县| 和林格尔县| 城固县| 香港 | 保山市| 肇源县| 盐山县| 油尖旺区| 铅山县| 江永县| 五河县| 教育| 渭南市| 新乡市| 五台县| 壤塘县| 杭州市| 蒲城县| 通许县| 寿阳县| 新乡市| 苗栗市| 清新县| 东辽县| 钟山县| 共和县| 永泰县| 绵阳市|