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

react?diff?算法實(shí)現(xiàn)思路及原理解析

 更新時(shí)間:2022年05月23日 14:41:51   作者:測(cè)不準(zhǔn)  
這篇文章主要介紹了react?diff?算法實(shí)現(xiàn)思路及原理解析,本節(jié)我們正式進(jìn)入基本面試必考的核心地帶?--?diff?算法,了解如何優(yōu)化和復(fù)用?dom?操作的,還有我們常見(jiàn)的?key?的作用,需要的朋友可以參考下

前面幾節(jié)我們學(xué)習(xí)了解了 react 的渲染機(jī)制和生命周期,本節(jié)我們正式進(jìn)入基本面試必考的核心地帶 -- diff 算法,了解如何優(yōu)化和復(fù)用 dom 操作的,還有我們常見(jiàn)的 key 的作用。

diff 算法使用在子都是數(shù)組的情況下,這點(diǎn)和 vue 是一樣的。如果元素是其他類型的話直接替換就好。

事例分析

按照之前的 diff 寫法,如果元素不同我們是直接刪了 a 再插入的:

按照上面圖的結(jié)構(gòu),我們需要知道那個(gè)元素變化了,其實(shí)右邊相對(duì)左邊只是把 A 做了移動(dòng),沒(méi)有 dom 元素的刪除和新增。

diff 特點(diǎn)

  • 同級(jí)對(duì)比 On
  • 類型不一樣銷毀老的,創(chuàng)建新的
  • 通過(guò) key 標(biāo)識(shí)

key 這里需要標(biāo)識(shí),主要是為了列表中有刪除新增時(shí)有優(yōu)化效果,如果純靜態(tài)列表,只是展示作用,key 意義不大。

diff 思路

  • 使用 map 存儲(chǔ)節(jié)點(diǎn)狀態(tài),格式如下:
let map = {
  keyA: ADOM,
  keyB: BDOM
}
  • 定義 lastPlacedIndex 記錄上一個(gè)不需要移動(dòng)的老節(jié)點(diǎn)

默認(rèn) lastPlacedIndex = 0 ,上一個(gè)不需要移動(dòng)的節(jié)點(diǎn),在循環(huán)新的子虛擬 dom 時(shí),如果老節(jié)點(diǎn)的掛載索引小于當(dāng)前值,則改變 lastPlacedIndex。這里有點(diǎn)類似 vue 的最長(zhǎng)遞增子序列,最大的保證不變的 dom 元素,只是判斷方式不同。

  • 循環(huán)新數(shù)組
  • 先出 A,map 中如果有 A,表示可以復(fù)用
    • 判斷 A 的老掛載索引和 lastPlacedIndex 對(duì)比,如果索引值大,A 節(jié)點(diǎn)不需要移動(dòng),更新 lastPlacedIndex 的值;否則循環(huán)到 B,掛載索引小,需要移動(dòng) B;循環(huán)到 G,map 中沒(méi)有值,需要新增;新的數(shù)組節(jié)點(diǎn)循環(huán)完,未用到的老節(jié)點(diǎn)全部刪除。

實(shí)現(xiàn) diff 算法

修改入口文件

// src/index.js
class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {list: ['A','B', 'C', 'D', 'E', 'F']}
  }
  handleClick = () => {
    this.setState({
      list: ['A', 'C', 'E', 'B', 'G']
    })
  }
  render() {
    // 使用空標(biāo)簽
    return <React.Fragment>
      <ul>
      {this.state.list.map(item => {
        // 這里使用 key 標(biāo)識(shí)
        return <li key={item}>{item}</li>
      })}
      </ul>
      <button onClick={this.handleClick}>add 1</button>
    </React.Fragment>
  }
}

實(shí)現(xiàn) React.Fragment

Fragment 就是代碼片段,不占用 dom 結(jié)構(gòu)。簡(jiǎn)寫 <></>,對(duì)應(yīng) dom 操作為 createDocumentFragment。

  • 是用原生庫(kù)打印,看結(jié)構(gòu)

可以發(fā)現(xiàn)就是一個(gè)簡(jiǎn)單的 Symbol,所以需要定義新的類型:

為什么一個(gè)簡(jiǎn)單的 Symbol 可以被渲染成片段呢?依賴于 babel 解析。

// src/constants.js
export const REACT_FRAGMENT = Symbol("react.fragment") // React.Fragment 標(biāo)簽
// 備用,diff 時(shí)做 patch 的 type 定義
// 新的插入
export const PLACEMENT = 'PLACEMENT'
// 復(fù)用的移動(dòng)
export const MOVE = 'MOVE'

在創(chuàng)建元素的時(shí)候進(jìn)行類型判斷,記得 react.js 中導(dǎo)出

// src/react-dom.js  
// createDOM 方法
else if (type === REACT_FRAGMENT) {
  // fragment 片段
  dom = document.createDocumentFragment()
}
// updateElement 方法
else if (oldVdom.type === REACT_FRAGMENT) {
  // fragment 不需要對(duì)比,直接對(duì)比 子 就可以了
  const currentDOM = newVdom.dom = findDOM(oldVdom)
    updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children)
}

我們需要修改 children 對(duì)比

之前邏輯:

// src/react-dom.js
// diff  沒(méi)有做復(fù)用,直接做的替換
function updateChildren(parentDOM, oldVChildren, newVChildren) {
  // 拿到最長(zhǎng)的
  let maxLength = Math.max(oldVChildren.length, newVChildren.length);
  for (let i = 0; i < maxLength; i++) {
  // 不能直接 appendChild 進(jìn)父,需要找到當(dāng)前操作的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。在其前面插入
    const nextVdom = oldVChildren.find((item, index) => index > i && item && findDOM(item))
    compareTwoVdom(parentDOM, oldVChildren[i], newVChildren[i], findDOM(nextVdom));
  }
}

新的邏輯(參考上面的流程):

// diff
function updateChildren(parentDOM, oldVChildren, newVChildren) {
  oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren];
  newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren];

 // 1.循環(huán)老結(jié)構(gòu), 構(gòu)建map存儲(chǔ)  key: dom
  const keydOldMap = {}
  let lastPlacedIndex = 0
  oldVChildren.forEach((oldVChild, index) => {
    let oldKey = oldVChild?.key || index //  寫key 了就用key,沒(méi)寫默認(rèn) index
    keydOldMap[oldKey] = oldVChild
  })
  // 2. 創(chuàng)建 dom 補(bǔ)丁包,收集 dom 操作
  const patch = []
  newVChildren.forEach((newVChild, index) => {
    newVChild.mountIndex = index // 為新元素每個(gè)添加索引標(biāo)識(shí)
    const newKey = newVChild?.key || index
    const oldVChild = keydOldMap[newKey] // 看有沒(méi)有存
    if(oldVChild) {
      // 如果有老的,就去更新老節(jié)點(diǎn) 這里直接可以復(fù)用
      updateElement(findDOM(oldVChild).parentNode, oldVChild, newVChild)
      if(oldVChild.mountIndex < lastPlacedIndex) {
        patch.push({
          type: MOVE,
          oldVChild,
          newVChild,
          mountIndex: index // 舊的移動(dòng)到新的的位置
        })
      }
      // 復(fù)用過(guò)了 刪除掉
      delete keydOldMap[newKey]
      lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex)// 取最大
    } else {
      // 新的
      patch.push({
        type: PLACEMENT,
        newVChild,
        mountIndex: index
      })
    }
  })
  // 找到需要移動(dòng)的老節(jié)點(diǎn)
  const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild)
  // 把要?jiǎng)h除的節(jié)點(diǎn) 和  要移動(dòng)的節(jié)點(diǎn)先全刪除     (頁(yè)面里沒(méi)有了,但是內(nèi)存中還存在  patch 中有存)
  Object.values(keydOldMap).concat(moveVChildren).forEach(oldVdom => {
    let currentDOM = findDOM(oldVdom)
    currentDOM.remove()
  })
  patch.forEach(action => {
    const {type, oldVChild, newVChild, mountIndex} = action
    // 老的真實(shí)子節(jié)點(diǎn)
    const childNodes = parentDOM.childNodes
    // 新的插入
    if (type === PLACEMENT) {
      let newDOM = createDOM(newVChild)
      let childNode = childNodes[mountIndex] // 老真實(shí)節(jié)點(diǎn)
      if (childNode) {
        // 往 老的父對(duì)應(yīng)位置插入
        parentDOM.insertBefore(newDOM, childNode)
      } else {
        parentDOM.appendChild(newDOM)
      }
    } else if (type === MOVE) {
      // 移動(dòng)不用創(chuàng)建 新 dom,復(fù)用
      let oldDOM = findDOM(oldVChild)
      let childNode = childNodes[mountIndex] // 老真實(shí)節(jié)點(diǎn)
      if (childNode) {
        // 往 老的父對(duì)應(yīng)位置插入
        parentDOM.insertBefore(oldDOM, childNode)
      } else {
        parentDOM.appendChild(oldDOM)
      }
    }
  })
}

實(shí)現(xiàn)如下跟原生一致,可以看到,三個(gè)節(jié)點(diǎn)實(shí)現(xiàn)了復(fù)用,即 A, C, E

如果沒(méi)有寫 key,我們?cè)诳葱Ч?/p>

可以看到只有第一個(gè)節(jié)點(diǎn)實(shí)現(xiàn)了復(fù)用,因?yàn)槟J(rèn)索引都使用的 0。所以這也是為什么不建議我們使用索引當(dāng) key 的原因。動(dòng)態(tài)列表 key 意義不大。

本節(jié)代碼不是很多,主要是 diff 算法的思路和實(shí)現(xiàn)原理。如果了解了 vuediff 算法,相信理解起來(lái)更好,也能更好的對(duì)比。下一小節(jié)我們學(xué)習(xí)下 react 新的生命周期。

到此這篇關(guān)于react diff 算法實(shí)現(xiàn)思路及原理解析的文章就介紹到這了,更多相關(guān)react diff 算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React性能優(yōu)化的實(shí)現(xiàn)方法詳解

    React性能優(yōu)化的實(shí)現(xiàn)方法詳解

    react憑借virtual DOM和diff算法擁有高效的性能,除此之外也有很多其他的方法和技巧可以進(jìn)一步提升react性能,在本文中我將列舉出可有效提升react性能的幾種方法,幫助我們改進(jìn)react代碼,提升性能
    2023-01-01
  • 詳解webpack2+React 實(shí)例demo

    詳解webpack2+React 實(shí)例demo

    本篇文章主要介紹了詳解webpack2+React 實(shí)例demo,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • React+Router多級(jí)導(dǎo)航切換路由方式

    React+Router多級(jí)導(dǎo)航切換路由方式

    這篇文章主要介紹了React+Router多級(jí)導(dǎo)航切換路由方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • react-diagram 序列化Json解讀案例分析

    react-diagram 序列化Json解讀案例分析

    今天帶來(lái)大家學(xué)習(xí)react-diagram 序列化Json解讀的相關(guān)知識(shí),本文通過(guò)多種案例給大家分析序列化知識(shí),通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2021-05-05
  • react MPA 多頁(yè)配置詳解

    react MPA 多頁(yè)配置詳解

    這篇文章主要介紹了react MPA 多頁(yè)配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • React并發(fā)更新與性能優(yōu)化解析

    React并發(fā)更新與性能優(yōu)化解析

    這篇文章主要為大家介紹了React并發(fā)更新與性能優(yōu)化解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • react開發(fā)教程之React 組件之間的通信方式

    react開發(fā)教程之React 組件之間的通信方式

    本篇文章主要介紹了react開發(fā)教程之React組件通信詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Vite搭建React項(xiàng)目的方法步驟

    Vite搭建React項(xiàng)目的方法步驟

    這篇文章主要介紹了Vite搭建React項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • React+Mobx基本使用、模塊化操作

    React+Mobx基本使用、模塊化操作

    React 和 MobX 是一對(duì)強(qiáng)力組合,React 通過(guò)提供機(jī)制把應(yīng)用狀態(tài)轉(zhuǎn)換為可渲染組件樹并對(duì)其進(jìn)行渲染,而MobX提供機(jī)制來(lái)存儲(chǔ)和更新應(yīng)用狀態(tài)供 React 使用,這篇文章主要介紹了React+Mobx基本使用、模塊化,需要的朋友可以參考下
    2022-09-09
  • React實(shí)現(xiàn)多標(biāo)簽在有限空間內(nèi)展示

    React實(shí)現(xiàn)多標(biāo)簽在有限空間內(nèi)展示

    在業(yè)務(wù)中,需要在一個(gè)卡片組件中展示多個(gè)標(biāo)簽,標(biāo)簽組件高度相同,寬度和出現(xiàn)順序不同,要求標(biāo)簽只能在有限的空間內(nèi)展示,所以本文給大家介紹了React實(shí)現(xiàn)多標(biāo)簽在有限空間內(nèi)展示,需要的朋友可以參考下
    2023-12-12

最新評(píng)論

满洲里市| 诸暨市| 太原市| 镇沅| 阳东县| 张掖市| 唐山市| 漳浦县| 乐山市| 沁阳市| 绥中县| 灵台县| 乐山市| 文登市| 台安县| 治多县| 新密市| 宁阳县| 昌吉市| 玉龙| 丰顺县| 来凤县| 额济纳旗| 苗栗县| 沙坪坝区| 上杭县| 淳化县| 闸北区| 青海省| 武平县| 马山县| 双柏县| 成安县| 朔州市| 博兴县| 巧家县| 张掖市| 松原市| 新民市| 永和县| 沁水县|