深入淺出 Vue 中的 key 值


key 的一個錯誤使用——使用 index 作為 key

不知道你在寫 v-for 的時候,會不會直接使用 index 作為它的 key 值,是的,我承認我會,不得不說,這真的不是一個好習慣。

以下是核心代碼,其中 arrData 的值為 [1,2,3,4]

<div id="sort"> <div v-for="(item,index) in arrData" :key="index" > <div>{{item}}</div> </div> </div> 
  mounted () { let el = document.getElementById('sort') var sortable = new Sortable(el, { onEnd: (e) => { const tempItem = this.arrData.splice(e.oldIndex, 1)[0] this.arrData.splice(e.newIndex, 0, tempItem) } }) } 

當然一開始的時候,數據渲染肯定是沒有問題的

 
 

好了,我們來看下以下的操作:


 
 

可以看到,我將3拖到2上面的時候,下面的數據變成了 1342,但是上面視圖的還是1234。然后我第四位置拖到第三位置的時候,下面的數據也是生效的,但是上面的數據似乎全部錯亂了。很好,我們重現了案發現場。

接着我改了綁定的 key 值,因為這里的例子比較特殊,我們就認為 item 的值都不相同

<div id="sort"> <div v-for="(item,index) in arrData" :key="item" > <div>{{item}}</div> </div> </div> 

再看效果:

 
 

是的,這個時候數據就完全跟視圖同步了。

為什么?

先看官方文檔中 key 的一句介紹

有相同父元素的子元素必須有獨特的 key。重復的 key 會造成渲染錯誤。

之所以會造成上面渲染錯誤的情況,是因為我們的 key 值不是獨特的,比如上面的 key 值,在調整數組順序后就每一項原來的 key 值都變了,所以導致了渲染錯誤。

我們先來得出一個結論,index 作為 key 值是有隱患的,除非你能保證 index 始終能夠能夠作為一個唯一的標識

key 值到底有什么用

vue2.0 之后,我們不寫 key 的話,就會報 warning,那也就是說官方是希望我們寫 key 值的,那么 key 到底在 vue 中扮演了什么樣的角色?

不使用 key 可以提高性能么
答案是,是的!可以!

先看官方解釋:

如果不使用 key,Vue 會使用一種最大限度減少動態元素並且盡可能的嘗試修復/再利用相同類型元素的算法。使用 key,它會基於 key 的變化重新排列元素順序,並且會移除 key 不存在的元素。

比如現在有一個數組 [1,2,3,4]變成了[2,1,3,4],那么沒有 key 的值會采取一種“就地更新策略”,見下圖。它不會移動元素節點的位置,而是直接修改元素本身,這樣就節省了一部分性能

 
 

而對於有 key 值的元素,它的更新方式如下圖所示。可以看到,這里它對 DOM 是移除/添加的操作,這是比較耗性能的。

 
 

竟然不帶 key 性能更優,為何還要帶 key
先來看一個例子,核心代碼如下,這里模仿一個切換 tab 的功能,也就是切換的tab1 是1,2,3,4。tab2 是 5,6,7,8。其中有設置了一個點擊設置第一項字體色為紅色的功能。

那么當我們點擊tab1將字體色設置成紅色之后,再切換到 tab2,我們預期的結果是我們第一項字體的初始顏色而不是紅色,但是結果卻還是紅色。

<div id="sort"> <button @click="trunToTab1">tab1</button> <button @click="trunToTab2">tab2</button> <div v-for="(item, index) in arrData"> <div @click="clickItem(index)" class="item">{{item}}</div> </div> </div> 
      trunToTab1 () { this.arrData = [1,2,3,4] }, trunToTab2 () { this.arrData = [5,6,7,8] }, clickItem () { document.getElementsByClassName('item')[0].style.color = 'red' } 
 
 

這就超出了我們的預期了,也就是官方文檔所說的,默認模式指的就是不帶 key 的狀態,對於依賴於子組件狀態或者臨時 DOM 狀態的,這種模式是不適用的。

這個默認的模式是高效的,但是只適用於不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出。

我們來看帶上 key 之后的效果

 
 

這就是官方文檔之所以推薦我們寫 key 的原因,根據文檔的介紹,如下:

使用 key,它會基於 key 的變化重新排列元素順序,並且會移除 key 不存在的元素。
它也可以用於強制替換元素/組件而不是重復使用它。當你遇到如下場景的時候它可能會很有用:

  • 完整地觸發組件的生命周期鈎子
  • 觸發過渡

那么 Vue 底層 key 值到底是怎么去做到以上的功能?我們就得聊聊 diff 算法以及虛擬 DOM 了。

key 在 diff 算法中的作用

這里我們不談 diff 算法的具體,只看 key 值在其中的作用。(diff 算法有機會我們再聊)

vue 源碼中 ``

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) 

我們整理一下代碼塊:

  // 如果有帶 key if (isUndef(oldKeyToIdx)) { // 創建 index 表 oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } if (isDef(newStartVnode.key)) { // 有 key ,直接從上面創建中獲取 idxInOld = oldKeyToIdx[newStartVnode.key] } else { // 沒有key, 調用 findIdxInOld idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); } 

那么最主要還是 createKeyToOldIdxfindIdxInOld 兩個函數的比較,那么他們做了什么呢?

function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map } 
 function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } } 

我們可以看到,如果我們有 key 值,我們就可以直接在 createKeyToOldIdx 方法中創建的 map 對象中根據我們的 key 值,直接找到相應的值。沒有 key 值,則需要遍歷才能拿到。相比於遍歷,映射的速度會更快。

key 值是每一個 vnode 的唯一標識,依靠 key,我們可以更快的拿到 oldVnode 中相對應的節點。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM