深刻淺出 Vue 中的 key 值

從前篇文章提及

前幾天我寫了一篇文章,sortable.js——Vue 數據更新問題 ,當時本身只是數據的強制刷新角度去分析,並且並沒找到真正的「元兇」。javascript

很感謝有人幫我指出,多是 Vuekey 值,致使數據渲染不正確的。由此,我作了進一步的嘗試。前端

key 的一個錯誤使用——使用 index 做爲 key

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

根據上篇文章,咱們仍是用 sortable.js 做爲例子討論。如下是核心代碼,其中 arrData 的值爲 [1,2,3,4]java

<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)
      }
    })
  }
複製代碼

固然一開始的時候,數據渲染確定是沒有問題的node

好了,咱們來看下如下的操做: git

能夠看到,我將3拖到2上面的時候,下面的數據變成了 1342,可是上面視圖的仍是1234。而後我第四位置拖到第三位置的時候,下面的數據也是生效的,可是上面的數據彷佛所有錯亂了。很好,咱們重現了案發現場。github

接着我改了綁定的 key 值,由於這裏的例子比較特殊,咱們就認爲 item 的值都不相同算法

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

再看效果:數組

是的,這個時候數據就徹底跟視圖同步了。bash

爲何?

先看官方文檔中 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 源碼中 src/core/vdom/patch.js

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 中相對應的節點。

參考

第 1 題:寫 React / Vue 項目時爲何要在列表組件中寫 key,其做用是什麼?

解析vue2.0的diff算法

歡迎你們關注個人前端大雜貨鋪~

歡迎你們關注個人前端大雜貨鋪~
相關文章
相關標籤/搜索