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()

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值