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

Vue 中為什么不推薦用index 做 key屬性值

 更新時間:2021年11月03日 10:48:47   作者:前端踐行者-Mr鵬帥  
Vue 中使用虛擬 dom 且根據(jù) diff 算法進行新舊 DOM 對比,從而更新真實 dom ,key 是虛擬 DOM 對象的唯一標識, 在 diff 算法中 key 起著極其重要的作用,本文講解 key 的作用以及為什么最好不要使用 index 作為 key 的屬性值

前言

前端開發(fā)中,只要涉及到列表渲染,那么無論是 React 還是 Vue 框架,都會提示或要求每個列表項使用唯一的 key,那很多開發(fā)者就會直接使用數(shù)組的 index 作為 key 的值,而并不知道 key 的原理。那么這篇文章就會講解 key 的作用以及為什么最好不要使用 index 作為 key 的屬性值。

key 的作用

Vue 中使用虛擬 dom 且根據(jù) diff 算法進行新舊 DOM 對比,從而更新真實 dom ,key 是虛擬 DOM 對象的唯一標識, 在 diff 算法中 key 起著極其重要的作用。

key 在 diff 算法中的角色

其實在 React,Vue 中 diff 算法大致是差不多,但是 diff 比對方式還是有較大差異的,甚至每個版本 diff 都大有不同。下面我們就以 Vue3.0 diff 算法為切入點,剖析 key 在 diff 算法中的作用

具體 diff 流程如下

圖片

Vue3.0 中 在 patchChildren 方法中有這么一段源碼

if (patchFlag > 0) {
      if (patchFlag & PatchFlags.KEYED_FRAGMENT) { 
         /* 對于存在 key 的情況用于 diff 算法 */
        patchKeyedChildren(
         ...
        )
        return
      } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
         /* 對于不存在 key 的情況,直接 patch  */
        patchUnkeyedChildren( 
          ...
        )
        return
      }
    }

patchChildren 根據(jù)是否存在 key 進行真正的 diff 或者直接 patch。對于 key 不存在的情況我們就不做深入研究了。

我們先來看看一些聲明的變量。

/*  c1 老的 vnode c2 新的vnode  */
let i = 0              /* 記錄索引 */
const l2 = c2.length   /* 新 vnode的數(shù)量 */
let e1 = c1.length - 1 /* 老 vnode 最后一個節(jié)點的索引 */
let e2 = l2 - 1        /* 新節(jié)點最后一個節(jié)點的索引 */

同步頭部節(jié)點

第一步的事情就是從頭開始尋找相同的 vnode,然后進行 patch,如果發(fā)現(xiàn)不是相同的節(jié)點,那么立即跳出循環(huán)。

//(a b) c
//(a b) d e
/* 從頭對比找到有相同的節(jié)點 patch ,發(fā)現(xiàn)不同,立即跳出*/
    while (i <= e1 && i <= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
        /* 判斷 key ,type 是否相等 */
      if (isSameVNodeType(n1, n2)) {
        patch(
          ...
        )
      } else {
        break
      }
      i++
    }

流程如下:

圖片

isSameVNodeType 作用就是判斷當前 vnode 類型 和 vnode 的 key 是否相等

export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
  return n1.type === n2.type && n1.key === n2.key
}

其實看到這,已經(jīng)知道 key 在 diff 算法的作用,就是用來判斷是否是同一個節(jié)點。

同步尾部節(jié)點

第二步從尾開始同前 diff

//a (b c)
//d e (b c)
/* 如果第一步?jīng)]有 patch 完,立即,從后往前開始 patch  如果發(fā)現(xiàn)不同立即跳出循環(huán) */
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
         ...
        )
      } else {
        break
      }
      e1--
      e2--
    }

經(jīng)歷第一步操作之后,如果發(fā)現(xiàn)沒有 patch 完,那么立即進行第二步,從尾部開始遍歷依次向前 diff。如果發(fā)現(xiàn)不是相同的節(jié)點,那么立即跳出循環(huán)。流程如下:

添加新的節(jié)點

第三步如果老節(jié)點是否全部 patch,新節(jié)點沒有被 patch 完,創(chuàng)建新的 vnode

//(a b)
//(a b) c
//i = 2, e1 = 1, e2 = 2
//(a b)
//c (a b)
//i = 0, e1 = -1, e2 = 0
/* 如果新的節(jié)點大于老的節(jié)點數(shù) ,對于剩下的節(jié)點全部以新的 vnode 處理(這種情況說明已經(jīng) patch 完相同的 vnode ) */
    if (i > e1) {
      if (i <= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i <= e2) {
          patch( /* 創(chuàng)建新的節(jié)點*/
            ...
          )
          i++
        }
      }
    }

流程如下:

圖片

刪除多余節(jié)點

第四步如果新節(jié)點全部被 patch,老節(jié)點有剩余,那么卸載所有老節(jié)點

//i > e2
//(a b) c
//(a b)
//i = 2, e1 = 2, e2 = 1
//a (b c)
//(b c)
//i = 0, e1 = 0, e2 = -1
else if (i > e2) {
   while (i <= e1) {
      unmount(c1[i], parentComponent, parentSuspense, true)
      i++
   }
}

流程如下:

最長遞增子序列

到了這一步,比較核心的場景還沒有出現(xiàn),如果運氣好,可能到這里就結(jié)束了,那我們也不能全靠運氣。剩下的一個場景是新老節(jié)點都還有多個子節(jié)點存在的情況。那接下來看看,Vue3 是怎么做的。為了結(jié)合 move、新增和卸載的操作

每次在對元素進行移動的時候,我們可以發(fā)現(xiàn)一個規(guī)律,如果想要移動的次數(shù)最少,就意味著需要有一部分元素是穩(wěn)定不動的,那么究竟能夠保持穩(wěn)定不動的元素有一些什么規(guī)律呢?

可以看一下上面這個例子:c  h  d  e  VS  d  e  i  c,在比對的時候,憑著肉眼可以看出只需要將 c 進行移動到最后,然后卸載 h,新增 i 就好了。d  e 可以保持不動,可以發(fā)現(xiàn) d  e 在新老節(jié)點中的順序都是不變的,d 在 e 的后面,下標處于遞增狀態(tài)。

這里引入一個概念,叫最長遞增子序列。
官方解釋:在一個給定的數(shù)組中,找到一組遞增的數(shù)值,并且長度盡可能的大。
有點比較難理解,那來看具體例子:
 
const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=> [2, 3, 7, 18]
這一列數(shù)組就是arr的最長遞增子序列,其實[2, 3, 7, 101]也是。
所以最長遞增子序列符合三個要求:
1、子序列內(nèi)的數(shù)值是遞增的
2、子序列內(nèi)數(shù)值的下標在原數(shù)組中是遞增的
3、這個子序列是能夠找到的最長的
但是我們一般會找到數(shù)值較小的那一組數(shù)列,因為他們可以增長的空間會更多。

那接下來的思路是:如果能找到老節(jié)點在新節(jié)點序列中順序不變的節(jié)點們,就知道,哪一些節(jié)點不需要移動,然后只需要把不在這里的節(jié)點插入進來就可以了。因為最后要呈現(xiàn)出來的順序是新節(jié)點的順序,移動是只要老節(jié)點移動,所以只要老節(jié)點保持最長順序不變,通過移動個別節(jié)點,就能夠跟它保持一致。 所以在此之前,先把所有節(jié)點都找到,再找對應的序列。最后其實要得到的則是這一個數(shù)組:[2, 3, 新增 , 0]。其實這就是 diff 移動的思路了

圖片

為什么不要用 index

性能消耗

使用 index 做 key,破壞順序操作的時候, 因為每一個節(jié)點都找不到對應的 key,導致部分節(jié)點不能復用,所有的新 vnode 都需要重新創(chuàng)建。

例子:

<template>
  <div class="hello">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}</li>
      <br>
      <button @click="addStudent">添加一條數(shù)據(jù)</button>
    </ul>
 
  </div>
</template>
 
<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      studentList: [
        { id: 1, name: '張三', age: 18 },
        { id: 2, name: '李四', age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: '王五', age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
</script>

我們先把 Chorme 調(diào)試器打開,我們雙擊把里面文本修改一下

圖片

我們運行以上上面的代碼,看下運行結(jié)果

圖片

從上面運行結(jié)果可以看出來,我們只是添加了一條數(shù)據(jù),但是三條數(shù)據(jù)都需要重新渲染是不是很驚奇,我明明只是插入了一條數(shù)據(jù),怎么三條數(shù)據(jù)都要重新渲染?而我想要的只是新增的那一條數(shù)據(jù)新渲染出來就行了。

上面我們也講過 diff 比較方式,下面根據(jù) diff 比較繪制一張圖,看看具體是怎么比較的吧

圖片

當我們在前面加了一條數(shù)據(jù)時 index 順序就會被打斷,導致新節(jié)點 key 全部都改變了,所以導致我們頁面上的數(shù)據(jù)都被重新渲染了。

下面我們下面生成 1000 個 DOM 來比較一下采用 index ,和不采用 index 性能比較,為了保證 key 的唯一性我們采用 uuid 作為 key

我們用 index 做為 key 先執(zhí)行一遍

<template>
  <div class="hello">
    <ul>
      <button @click="addStudent">添加一條數(shù)據(jù)</button>
      <br>
      <li v-for="(item,index) in studentList" :key="index">{{item.id}}</li>
    </ul>
  </div>
</template>
 
<script>
import uuidv1 from 'uuid/v1'
export default {
  name: 'HelloWorld',
  data() {
    return {
      studentList: [{id:uuidv1()}],
    };
  },
  created(){
    for (let i = 0; i < 1000; i++) {
      this.studentList.push({
        id: uuidv1(),
      });
    }
  },
  beforeUpdate(){
    console.time('for');
  },
  updated(){
    console.timeEnd('for')//for: 75.259033203125 ms
  },
  methods:{
    addStudent(){
      const studentObj = { id: uuidv1() };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
</script>

換成 id 作為 key

<template>
  <div class="hello">
    <ul>
      <button @click="addStudent">添加一條數(shù)據(jù)</button>
      <br>
      <li v-for="(item,index) in studentList" :key="item.id">{{item.id}}</li>
    </ul>
  </div>
</template>
  beforeUpdate(){
    console.time('for');
  },
  updated(){
    console.timeEnd('for')//for: 42.200927734375 ms
  },

從上面比較可以看出,用唯一值作為 key 可以節(jié)約開銷

數(shù)據(jù)錯位

上述例子可能覺得用 index 做 key 只是影響頁面加載的效率,認為少量的數(shù)據(jù)影響不大,那下面這種情況,用 index 就可能出現(xiàn)一些意想不到的問題了,還是上面的場景,這時我先再每個文本內(nèi)容后面加一個 input 輸入框,并且手動在輸入框內(nèi)填寫一些內(nèi)容,然后通過 button 向前追加一位同學看看

<template>
  <div class="hello">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}<input /></li>
      <br>
      <button @click="addStudent">添加一條數(shù)據(jù)</button>
    </ul>
  </div>
</template>
 
<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      studentList: [
        { id: 1, name: '張三', age: 18 },
        { id: 2, name: '李四', age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: '王五', age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
</script>

我們往 input 里面輸入一些值,添加一位同學看下效果:

圖片

這時候我們就會發(fā)現(xiàn),在添加之前輸入的數(shù)據(jù)錯位了。添加之后王五的輸入框殘留著張三的信息,這很顯然不是我們想要的結(jié)果。

圖片

從上面比對可以看出來這時因為采用 index 作為 key 時,當在比較時,發(fā)現(xiàn)雖然文本值變了,但是當繼續(xù)向下比較時發(fā)現(xiàn) Input DOM 節(jié)點還是和原來一摸一樣,就復用了,但是沒想到 input 輸入框殘留輸入的值,這時候就會出現(xiàn)輸入的值出現(xiàn)錯位的情況

解決方案

既然知道用 index 在某些情況下帶來很不好的影響,那平時我們在開發(fā)當中怎么去解決這種情況呢?其實只要保證 key 唯一不變就行,一般在開發(fā)中用的比較多就是下面三種情況。

  • 在開發(fā)中最好每條數(shù)據(jù)使用唯一標識固定的數(shù)據(jù)作為 key,比如后臺返回的 ID,手機號,身份證號等唯一值
  • 可以采用 Symbol 作為 key,Symbol 是 ES6 引入了一種新的原始數(shù)據(jù)類型 Symbol ,表示獨一無二的值,最大的用法是用來定義對象的唯一屬性名。
let a=Symbol('測試')
let b=Symbol('測試')
console.log(a===b)//false

可以采用 uuid 作為 key ,uuid 是 Universally Unique Identifier 的縮寫,它是在一定的范圍內(nèi)(從特定的名字空間到全球)唯一的機器生成的標識符

我們采用上面第一種方案作為 key 再看一下上面情況,如圖所示。key 相同的節(jié)點都做到了復用。起到了diff 算法的真正作用。

圖片

圖片

圖片

總結(jié)

  • 用 index 作為 key 時,在對數(shù)據(jù)進行,逆序添加,逆序刪除等破壞順序的操作時,會產(chǎn)生沒必要的真實 DOM更新,從而導致效率低
  • 用 index 作為 key 時,如果結(jié)構(gòu)中包含輸入類的 DOM,會產(chǎn)生錯誤的 DOM 更新
  • 在開發(fā)中最好每條數(shù)據(jù)使用唯一標識固定的數(shù)據(jù)作為 key,比如后臺返回的 ID,手機號,身份證號等唯一值
  • 如果不存在對數(shù)據(jù)逆序添加,逆序刪除等破壞順序的操作時,僅用于渲染展示用時,使用 index 作為 key 也是可以的(但是還是不建議使用,養(yǎng)成良好開發(fā)習慣)。

到此這篇關于在 Vue 中為什么不推薦用 index 做 key的文章就介紹到這了,更多相關Vue index 做 key內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Vue項目中使用Base64

    Vue項目中使用Base64

    在vue項目中有時會使用到Base6464轉(zhuǎn)碼,現(xiàn)將自己使用的一種方法記錄下來,對vue使用Base64相關知識感興趣的朋友一起看看吧
    2024-02-02
  • vue鍵盤事件點擊事件加native操作

    vue鍵盤事件點擊事件加native操作

    這篇文章主要介紹了vue鍵盤事件點擊事件加native操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • VUE重點問題總結(jié)

    VUE重點問題總結(jié)

    本篇內(nèi)容給大家總結(jié)了VUE的重要難點,并把代碼做了詳細分享,有興趣的朋友參考學習下。
    2018-03-03
  • Vue3+vite創(chuàng)建項目方式

    Vue3+vite創(chuàng)建項目方式

    本文介紹了如何使用Vite創(chuàng)建Vue項目,包括版本升級、命令變化以及配置vue-router、Vuex和AntDesignVue的方法,同時,也提供了降級Vue?CLI以兼容Vue2項目的步驟
    2024-12-12
  • Vue實現(xiàn)左右菜單聯(lián)動實現(xiàn)代碼

    Vue實現(xiàn)左右菜單聯(lián)動實現(xiàn)代碼

    這篇文章主要介紹了Vue實現(xiàn)左右菜單聯(lián)動實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • vue 自定義全局方法,在組件里面的使用介紹

    vue 自定義全局方法,在組件里面的使用介紹

    下面小編就為大家介紹一下vue 自定義全局方法,在組件里面的使用。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • vue中checkbox如何修改為圓形樣式

    vue中checkbox如何修改為圓形樣式

    這篇文章主要介紹了vue中checkbox如何修改為圓形樣式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 在Vue中實現(xiàn)網(wǎng)頁截圖與截屏功能詳解

    在Vue中實現(xiàn)網(wǎng)頁截圖與截屏功能詳解

    在Web開發(fā)中,有時候需要對網(wǎng)頁進行截圖或截屏,Vue作為一個流行的JavaScript框架,提供了一些工具和庫,可以方便地實現(xiàn)網(wǎng)頁截圖和截屏功能,本文將介紹如何在Vue中進行網(wǎng)頁截圖和截屏,需要的朋友可以參考下
    2023-06-06
  • vue2手機APP項目添加開屏廣告或者閃屏廣告

    vue2手機APP項目添加開屏廣告或者閃屏廣告

    這篇文章主要為大家詳細介紹了vue2手機APP項目添加開屏廣告或者閃屏廣告的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • vue+iview分頁組件的封裝

    vue+iview分頁組件的封裝

    這篇文章主要為大家詳細介紹了vue+iview分頁組件的封裝,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-11-11

最新評論

灌云县| 八宿县| 竹溪县| 台南县| 潼南县| 宾阳县| 桓台县| 合作市| 邹城市| 河池市| 特克斯县| 德清县| 鹿泉市| 贞丰县| 三穗县| 漳州市| 商城县| 湘潭市| 遂川县| 铁岭市| 河北区| 大城县| 买车| 晋城| 太保市| 新绛县| 柯坪县| 武夷山市| 昌都县| 宝兴县| 稻城县| 闻喜县| 武威市| 读书| 民县| 大埔区| 思南县| 合山市| 拉孜县| 芜湖市| 望奎县|