Vue 2.x 子組件刪除時使用setTimeout導致splice數組元素後視圖沒有正確更新的解決方案

在實際業務中,經常會有這樣的場景:一堆消息卡片。比如:
在這裏插入圖片描述
對於這種情況,在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()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章