前幾天我寫了一篇文章,sortable.js——Vue 數據更新問題 ,當時本身只是數據的強制刷新角度去分析,並且並沒找到真正的「元兇」。javascript
很感謝有人幫我指出,多是 Vue
的 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
始終可以可以做爲一個惟一的標識
在 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
了。
這裏咱們不談 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);
}
複製代碼
那麼最主要仍是 createKeyToOldIdx
和 findIdxInOld
兩個函數的比較,那麼他們作了什麼呢?
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,其做用是什麼?
歡迎你們關注個人前端大雜貨鋪~