欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

key 的作用

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

key 在 diff 算法中的角色

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

具體 diff 流程如下

圖片

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

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

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

我們先來(lái)看看一些聲明的變量。

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

同步頭部節(jié)點(diǎn)

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

//(a b) c
//(a b) d e
/* 從頭對(duì)比找到有相同的節(jié)點(diǎn) 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 作用就是判斷當(dāng)前 vnode 類型 和 vnode 的 key 是否相等

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

其實(shí)看到這,已經(jīng)知道 key 在 diff 算法的作用,就是用來(lái)判斷是否是同一個(gè)節(jié)點(diǎn)。

同步尾部節(jié)點(diǎn)

第二步從尾開(kāi)始同前 diff

//a (b c)
//d e (b c)
/* 如果第一步?jīng)]有 patch 完,立即,從后往前開(kāi)始 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)沒(méi)有 patch 完,那么立即進(jìn)行第二步,從尾部開(kāi)始遍歷依次向前 diff。如果發(fā)現(xiàn)不是相同的節(jié)點(diǎn),那么立即跳出循環(huán)。流程如下:

添加新的節(jié)點(diǎn)

第三步如果老節(jié)點(diǎn)是否全部 patch,新節(jié)點(diǎn)沒(méi)有被 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é)點(diǎn)大于老的節(jié)點(diǎn)數(shù) ,對(duì)于剩下的節(jié)點(diǎn)全部以新的 vnode 處理(這種情況說(shuō)明已經(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é)點(diǎn)*/
            ...
          )
          i++
        }
      }
    }

流程如下:

圖片

刪除多余節(jié)點(diǎn)

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

//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++
   }
}

流程如下:

最長(zhǎng)遞增子序列

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

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

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

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

那接下來(lái)的思路是:如果能找到老節(jié)點(diǎn)在新節(jié)點(diǎn)序列中順序不變的節(jié)點(diǎn)們,就知道,哪一些節(jié)點(diǎn)不需要移動(dòng),然后只需要把不在這里的節(jié)點(diǎn)插入進(jìn)來(lái)就可以了。因?yàn)樽詈笠尸F(xiàn)出來(lái)的順序是新節(jié)點(diǎn)的順序,移動(dòng)是只要老節(jié)點(diǎn)移動(dòng),所以只要老節(jié)點(diǎn)保持最長(zhǎng)順序不變,通過(guò)移動(dòng)個(gè)別節(jié)點(diǎn),就能夠跟它保持一致。 所以在此之前,先把所有節(jié)點(diǎn)都找到,再找對(duì)應(yīng)的序列。最后其實(shí)要得到的則是這一個(gè)數(shù)組:[2, 3, 新增 , 0]。其實(shí)這就是 diff 移動(dòng)的思路了

圖片

為什么不要用 index

性能消耗

使用 index 做 key,破壞順序操作的時(shí)候, 因?yàn)槊恳粋€(gè)節(jié)點(diǎn)都找不到對(duì)應(yīng)的 key,導(dǎo)致部分節(jié)點(diǎn)不能復(fù)用,所有的新 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)試器打開(kāi),我們雙擊把里面文本修改一下

圖片

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

圖片

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

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

圖片

當(dāng)我們?cè)谇懊婕恿艘粭l數(shù)據(jù)時(shí) index 順序就會(huì)被打斷,導(dǎo)致新節(jié)點(diǎn) key 全部都改變了,所以導(dǎo)致我們頁(yè)面上的數(shù)據(jù)都被重新渲染了。

下面我們下面生成 1000 個(gè) DOM 來(lái)比較一下采用 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é)約開(kāi)銷

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

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

<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 里面輸入一些值,添加一位同學(xué)看下效果:

圖片

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

圖片

從上面比對(duì)可以看出來(lái)這時(shí)因?yàn)椴捎?index 作為 key 時(shí),當(dāng)在比較時(shí),發(fā)現(xiàn)雖然文本值變了,但是當(dāng)繼續(xù)向下比較時(shí)發(fā)現(xiàn) Input DOM 節(jié)點(diǎn)還是和原來(lái)一摸一樣,就復(fù)用了,但是沒(méi)想到 input 輸入框殘留輸入的值,這時(shí)候就會(huì)出現(xiàn)輸入的值出現(xiàn)錯(cuò)位的情況

解決方案

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

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

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

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

圖片

圖片

圖片

總結(jié)

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

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

相關(guān)文章

  • 解決antd Form 表單校驗(yàn)方法無(wú)響應(yīng)的問(wèn)題

    解決antd Form 表單校驗(yàn)方法無(wú)響應(yīng)的問(wèn)題

    這篇文章主要介紹了解決antd Form 表單校驗(yàn)方法無(wú)響應(yīng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • Vue實(shí)現(xiàn)雙向綁定的方法

    Vue實(shí)現(xiàn)雙向綁定的方法

    這篇文章主要介紹了Vue實(shí)現(xiàn)雙向綁定的方法,了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • vue中的v-show,v-if,v-bind的使用示例詳解

    vue中的v-show,v-if,v-bind的使用示例詳解

    這篇文章主要介紹了vue中的v-show,v-if,v-bind的使用,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • VUE中如何調(diào)用高德地圖獲取當(dāng)前位置(VUE2.0和3.0通用)

    VUE中如何調(diào)用高德地圖獲取當(dāng)前位置(VUE2.0和3.0通用)

    使用uniapp開(kāi)發(fā)微信小程序時(shí),多多少少會(huì)遇到獲取當(dāng)前位置的詳細(xì)信息,下面這篇文章主要給大家介紹了關(guān)于VUE中如何調(diào)用高德地圖獲取當(dāng)前位置(VUE2.0和3.0通用)的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • vue cli 3.0通用打包配置代碼,不分一二級(jí)目錄

    vue cli 3.0通用打包配置代碼,不分一二級(jí)目錄

    這篇文章主要介紹了vue cli 3.0通用打包配置代碼,不分一二級(jí)目錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • Vue ElementUI中el-table表格嵌套樣式問(wèn)題小結(jié)

    Vue ElementUI中el-table表格嵌套樣式問(wèn)題小結(jié)

    這篇文章主要介紹了Vue ElementUI中el-table表格嵌套樣式問(wèn)題小結(jié),兩個(gè)表格嵌套,當(dāng)父表格有children數(shù)組時(shí)子表格才展示,對(duì)Vue ElementUI中el-table表格嵌套樣式問(wèn)題感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • Vue分頁(yè)效果與購(gòu)物車功能

    Vue分頁(yè)效果與購(gòu)物車功能

    這篇文章主要介紹了Vue分頁(yè)效果與購(gòu)物車功能,本文圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-12-12
  • vue 移動(dòng)端記錄頁(yè)面瀏覽位置的方法

    vue 移動(dòng)端記錄頁(yè)面瀏覽位置的方法

    這篇文章主要介紹了vue 移動(dòng)端記錄頁(yè)面瀏覽位置的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • <el-button>點(diǎn)擊后如何跳轉(zhuǎn)指定url鏈接

    <el-button>點(diǎn)擊后如何跳轉(zhuǎn)指定url鏈接

    這篇文章主要介紹了<el-button>點(diǎn)擊后如何跳轉(zhuǎn)指定url鏈接問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • vue組件文檔生成備注詳解

    vue組件文檔生成備注詳解

    這篇文章主要介紹了vue組件文檔生成備注詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04

最新評(píng)論