在實際業務中,經常會有這樣的場景:一堆消息卡片。比如:
對於這種情況,在Vue裏,我們一般都會封裝一個Card組件,然後在父組件中用v-for進行渲染。以下是一個隨手寫的例子(也可以在https://codesandbox.io/s/great-field-x95lw?fontsize=14&hidenavigation=1&theme=dark查看):
// App.vue
<template>
<div id="app">
<div v-for="(item, index) in arr"
:key="index">
<Card :title="item.name"
@remove="arr.splice(index, 1)"/>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: { Card },
data () {
return {
arr: [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' },
{ id: 4, name: 'buz' }
]
}
}
}
</script>
// Card.vue
<template>
<div v-if="showCard"
:style="{border: '1px solid red'}">
<span>{{title}} </span>
<button @click="removeCard">delete</button>
</div>
</template>
<script>
export default {
name: 'Card',
props: {
title: String
},
data () {
return {
showCard: true
}
},
methods: {
removeCard () {
this.$emit('remove')
setTimeout(() => {
this.showCard = false
// 此處展示刪除動畫
}, 500)
}
}
}
</script>
效果是這樣的,看起來是磕磣了一點,但是和之前那個卡片比起來也就差個CSS,對吧:
太簡單了!但是就這麼一個簡單的過程,但是裏面卻隱藏着一個bug:除了最後一張卡片,點擊刪除的時候,不僅僅會刪除當前元素,還會刪除其他的卡片。比如,我們點擊刪除第一張卡片:
第二個元素也沒了。
這個問題當時困擾了我很久,索性去官方文檔看看,果然找到了解釋:
key
的特殊屬性主要用在 Vue 的虛擬 DOM 算法,在新舊 nodes 對比時辨識 VNodes。如果不使用 key,Vue 會使用一種最大限度減少動態元素並且儘可能的嘗試就地修改/複用相同類型元素的算法。而使用 key 時,它會基於 key 的變化重新排列元素順序,並且會移除 key 不存在的元素。有相同父元素的子元素必須有獨特的 key。重複的 key 會造成渲染錯誤。
再次回去查看邏輯,就會發現,這裏因爲採用了數組下標index作爲key,這個key會根據數組中元素下標的變化而變化。點擊刪除後,父組件監聽到Card的remove事件,就會從數組中移除對應index的元素,key隨之變化,元素被重新排序,被刪除元素的後一個元素的key就變成了被刪除元素的key。此時0.5s的動畫正在進行,等到動畫結束後,showCard被置爲false,但是此時的元素已經變成預定目標的後一個元素了(只認key不認內容),所以就會出現刪除兩個的情況。整個流程是這樣的:
- 點擊刪除
- 觸發remove事件
- 從數組中移除預定目標,key發生改變
- 視圖重新渲染
- 動畫結束,觸發v-if,銷燬目標的後一個元素
這個現象其實在很多別的語言裏也會出現,比如Java的List.remove()
。