亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue中不推薦用index做key的原因有哪些

發布時間:2021-11-30 09:02:52 來源:億速云 閱讀:151 作者:小新 欄目:編程語言

這篇文章主要介紹了Vue中不推薦用index做key的原因有哪些,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

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

key 的作用

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

key 在 diff 算法中的角色

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

具體 diff 流程如下

Vue中不推薦用index做key的原因有哪些

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 根據是否存在 key 進行真正的 diff 或者直接 patch。對于 key 不存在的情況我們就不做深入研究了。

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

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

同步頭部節點

第一步的事情就是從頭開始尋找相同的 vnode,然后進行 patch ,如果發現不是相同的節點,那么立即跳出循環。

//(a b) c
//(a b) d e
/* 從頭對比找到有相同的節點 patch ,發現不同,立即跳出*/
    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++
    }

流程如下:

Vue中不推薦用index做key的原因有哪些

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

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

其實看到這,是不是已經知道 key 在 diff 算法的作用,就是用來判斷是否是同一個節點。

同步尾部節點

第二步從尾開始同前 diff

//a (b c)
//d e (b c)
/* 如果第一步沒有 patch 完,立即,從后往前開始 patch  如果發現不同立即跳出循環 */
    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--
    }

經歷第一步操作之后,如果發現沒有 patch 完,那么立即進行第二步,從尾部開始遍歷依次向前 diff。如果發現不是相同的節點,那么立即跳出循環。 流程如下:

Vue中不推薦用index做key的原因有哪些

添加新的節點

第三步如果老節點是否全部 patch,新節點沒有被 patch 完,創建新的 vnode

//(a b)
//(a b) c
//i = 2, e1 = 1, e2 = 2
//(a b)
//c (a b)
//i = 0, e1 = -1, e2 = 0
/* 如果新的節點大于老的節點數 ,對于剩下的節點全部以新的 vnode 處理(這種情況說明已經 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( /* 創建新的節點*/
            ...
          )
          i++
        }
      }
    }

流程如下:

Vue中不推薦用index做key的原因有哪些

刪除多余節點

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

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

流程如下:

Vue中不推薦用index做key的原因有哪些

最長遞增子序列

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

Vue中不推薦用index做key的原因有哪些

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

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

這里引入一個概念,叫最長遞增子序列。
官方解釋:在一個給定的數組中,找到一組遞增的數值,并且長度盡可能的大。
有點比較難理解,那來看具體例子:

const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=> [2, 3, 7, 18]
這一列數組就是arr的最長遞增子序列,其實[2, 3, 7, 101]也是。
所以最長遞增子序列符合三個要求:
1、子序列內的數值是遞增的
2、子序列內數值的下標在原數組中是遞增的
3、這個子序列是能夠找到的最長的
但是我們一般會找到數值較小的那一組數列,因為他們可以增長的空間會更多。

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

Vue中不推薦用index做key的原因有哪些

為什么不要用index

性能消耗

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

例子:

<template>
  <div class="hello">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}</li>
      <br>
      <button @click="addStudent">添加一條數據</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 調試器打開,我們雙擊把里面文本修改一下

Vue中不推薦用index做key的原因有哪些

我們運行以上上面的代碼,看下運行結果

Vue中不推薦用index做key的原因有哪些

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

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

Vue中不推薦用index做key的原因有哪些

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

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

我們用 index 做為 key 現執行一遍

<template>
  <div class="hello">
    <ul>
      <button @click="addStudent">添加一條數據</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">添加一條數據</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 可以節約開銷

數據錯位

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

<template>
  <div class="hello">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}<input /></li>
      <br>
      <button @click="addStudent">添加一條數據</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 里面輸入一些值,添加一位同學看下效果:

Vue中不推薦用index做key的原因有哪些

這時候我們就會發現,在添加之前輸入的數據錯位了。添加之后王五的輸入框殘留著張三的信息,這很顯然不是我們想要的結果。

Vue中不推薦用index做key的原因有哪些

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

解決方案

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

  • 在開發中最好每條數據使用唯一標識固定的數據作為 key,比如后臺返回的 ID,手機號,身份證號等唯一值

  • 可以采用 Symbol 作為 key,Symbol 是 ES6 引入了一種新的原始數據類型 Symbol ,表示獨一無二的值,最大的用法是用來定義對象的唯一屬性名。

let a=Symbol('測試')
let b=Symbol('測試')
console.log(a===b)//false
  • 可以采用 uuid 作為 key ,uuid 是 Universally Unique Identifier 的縮寫,它是在一定的范圍內(從特定的名字空間到全球)唯一的機器生成的標識符

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

Vue中不推薦用index做key的原因有哪些Vue中不推薦用index做key的原因有哪些

Vue中不推薦用index做key的原因有哪些

感謝你能夠認真閱讀完這篇文章,希望小編分享的“Vue中不推薦用index做key的原因有哪些”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湘西| 濉溪县| 团风县| 宜兰市| 沧州市| 永登县| 仪征市| 万年县| 沽源县| 老河口市| 沈阳市| 平昌县| 常山县| 名山县| 当涂县| 仁寿县| 香港| 泰安市| 思茅市| 涿鹿县| 卢湾区| 调兵山市| 齐河县| 咸丰县| 海阳市| 广德县| 沁源县| 和龙市| 台东市| 靖远县| 枣阳市| 岳西县| 柞水县| 慈利县| 聊城市| 阳泉市| 庄浪县| 宜城市| 东港市| 光山县| 德令哈市|