[vuejs/vue]v-for 与 transition 同时应用时频繁更新下元素存在清理不干净的情况

2023-12-09 536 views
7

Vue.js version

1.0.18

Reproduction Link

https://jsfiddle.net/ClassicOldSong/1utjmu60/5/

操作方法:点击Start filling,然后点击Start removing,观测结果即可 remove的时间间隔比fill要小,也就是说不应该出现多余残留的元素 而且这里的remove是直接清空数组,更不应该产生残留了 然而你仍然能看到元素慢慢地变多起来

console里有输出beforeLeave和afterLeave的触发次数,可以发现afterLeave的次数小于beforeLeave

Steps to reproduce

用v-for="data in data"创建一个列表模板,设置transition属性 至少创建退出动画,进入动画无关这个问题 (为了使效果更加明显建议退出后状态不要将高度设置为0或者将透明度设置为0) 创建$vm,绑定到刚刚创建的模板

情况1

连续快速地重复向$vm.data中添加然后删除对象的动作多次

情况2

向$vm.data中添加大量对象 然后清空$vm.data 然后立即向$vm.data中添加对象 连续快速地重复以上步骤多次

正常情况下情况2的效果比情况1明显一些

What is Expected?

元素被正常地移除并不再出现在Document内

What is actually happening?

部分元素残留在页面内没有被清除且仍然能够正常地响应事件 经过测试发现afterLeave事件被触发的次数有很大的几率比beforeLeave和leave事件的触发次数小,尤其在低性能设备上比如手机会更加严重 beforeLeave事件触发次数与leave事件触发次数始终保持一致,初步判断为leave事件的回调没有被正确地执行或者在执行前就被清理掉了

请尽快修复这个BUG!!谢谢!

回答

0

我觉得 这应该不是 vue的 一个bug。

而是使用上存在一些「未知」的问题。

比如 : 你的 css leave 的动画 没有添加,导致 「退场」动画没有执行。

所以安装 vue 中的 transitionend or annimationend 没有执行, 进而导致 元素会 出现如你描述的「清理不干净」的情况。

这个问题 我在之前的开发中也遇到过,检查后确定是自己的问题。

建议你先自检一遍,如果还有这样的问题。 建议 可以在 jsfiddle.net 上使用示例代码的方式来更方便的 debug

5

实际上退场动画已经执行了,因为部分元素已经退出,还残留了一部分元素没有被清理。 没有被清理的元素的CSS退场动画也执行完毕,唯一缺少的就是将元素从document中删除 用js钩子检测发现最后一步afterLeave并没有被触发 刚刚又试了一下发现其实stagger貌似能防止一点,但具体能不能完全防止这个问题还不知道

2

stagger并没有解决问题。。。。

3

我机器按你的两个描述都无法重现,请上代码吧。

4

@yyx990803

Reproduction Link

https://jsfiddle.net/ClassicOldSong/1utjmu60/5/

操作方法:点击Start filling,然后点击Start removing,观测结果即可 remove的时间间隔比fill要小,也就是说不应该出现多余残留的元素 而且这里的remove是直接清空数组,更不应该产生残留了 然而你仍然能看到元素慢慢地变多起来

console里有输出beforeLeave和afterLeave的触发次数,可以发现afterLeave的次数小于beforeLeave

6

我认为你的demo产生的问题.... 和vue没有关系啊...

9

@taoche 但是这里没有任何其他途径操作元素啊。。。所以你觉得问题出在了哪里。。。

0

你好,这个问题出现在于你的css中,并不是data的问题。 .trans-transition { transition: opacity .5s; opacity: 1; } 想一下,虽然你添加数据是每隔51毫秒,但是它的动画效果将会持续500毫秒,那么是不是会造成效果还没结束,数据已经被清除? 导致有些元素会继续留在doc上,但是数据上无法再去清除它。而其他效果已经结束的元素就会被很好的清除

只需要调整css效果时间,或者考虑改变下你应用的机制。我想知道了哪里出的问题,你应该很快能找到解决方案。

8

@EveLuty 实际上Vue的工作机制是这样的:当发现你的数据变动以后才会去进行元素操作比如添加或者移除。而我遇到的问题是Vue已经发现了数据变化,而且知道这个元素需要被清除,已经执行了退场动画,却在最后一步的处理上出了问题。所以我并不是十分认同你的看法。

4

你所谓的最后一步的处理出了问题,并不然,持续产生阴影的原因是你在dome中永不停歇的push。所以for一直在执行。即使你使得data=[], 那么数据也会在你设定好的动画中渐进消失,在动画结束之前并不会立刻清空,而你又持续push,所以导致这样。 你可以打开控制台看下,可以观察到只有当过场动画的class依次都变完之后,该元素才会消失,数据也才会消失。

而阴影滞留呢,无一例外都是data设为空集合之后新添加数据造成的。是因为是新push的数据还未完全走完过渡效果,就原数据被清空造成的。导致在离开效果进行时,已经丢失了跟原数据的绑定。但是这种应用案例应该并不多吧。

由css过渡效果时间引起的问题。我不确定这只发生于vuejs,或者是针对所有类似情况都会发生。

最后,有两个方法可以解决这个问题, 一是取消效果的延迟效果(从此也看出了问题出在transition的时间上) trans-transition { transition: opacity 0s; opacity: 1; } 但是你可能想要保留渐进效果。 那么可以尝试用第二种方法,来避免因为数据的删除,而导致动画有可能会终止于leave的状态(即opacity:0.1)

.trans-transition {
    transition: opacity .5s;
    opacity: 1;
}
.trans-enter{
    opacity: 0.1;
}

.trans-leave {
    opacity: 0;
}

这种方案在使用后,依然能看到有三到四行残留,是因为你在不停push,正常现象。

就这样了。如果还有问题,就等尤大大回复吧~~

2

@EveLuty 我设置为opacity: 0.1只是为了更直观地展现这个问题。。因为在实际应用中我的处理方式就是opacity: 0。具体应用场景我在回复YidingW的时候已经说明,我无法阻止用户在动画结束之前向里面推送新的消息。而且你可以尝试一下将 data=[] 换成 data.splice(data.length - 1, 1) 之类的操作方式,一样会出现问题(比如https://jsfiddle.net/ClassicOldSong/1utjmu60/7/ ,stop后依旧会有残留,而且让我很纳闷的是,数据绑定如果已经丢失的话为何还能继续响应click事件)。

所以估计只能等待尤大大回复了。。。

7

恩,按opacity:0的方式就都没有问题了。

问题还是在于动画时间导致数据删除并不及时,我又测试了下,如果你在start的里面加入简单的console查看data的长度,会有概率出现等于2的时候,就是说在push之前,数据中并不为空,也验证了我说的,清空在更短周期内执行,并不代表会比push之前完成清空数组的任务,导致出现这种问题。

(另外,如果这两个方法的时间周期如果一致,也不会出现问题,并且动画数量也一致)

至于最后的疑问,数据绑定丢失,但是你的html中该元素的click事件还是指向vue里的方法呀 :) 你可以看看。

1

@EveLuty 我的意思是。。。我在opacity: 0的时候发现了这个问题。。。。我纳闷的是传入参数而不是click事件本身,也就是说Vue内部的引用对象其实也没有被清理干净 有空仔细扒一下Vue源码好了。。 不过确实像我这样奇葩的应用场景的确很罕见,然而确实有问题 顺便实际场景下用户的操作会比这里的模拟要随机的多 另外现在我对于残留元素的处理方式是用jQuery删掉((

补充:刚刚尝试了一下在fill的循环里检测datas长度,无论是直接vm.datas还是vm.$data.datas的长度都没有发现不为0的情况,在我看来以及从数学的角度上来分析是fill之前已经清空了,麻烦指点一下如何正确地追踪。。。

9

首先,你真的按我的css去试过嘛? 你去试试,一旦点击stop后不会有一点残留。难道这不是你要的结果么 在点击stop之前依然能看到有三到四行残留刷新着,是因为你在不停push,正常现象。stop后不会有无法remove的’阴影‘

*刚测试了下,不用非得opacity等于0,只要entry和leave不同即可(当然leave的opacity不能为1)。leave的opacity越小,表现越好。但是stop之后效果都能达到,无不可移除的阴影元素

2

追踪么,不要加太多语句;就这样,然后在稍微低一点的性能下测试就会很明显的发现问题。就像你说的手机性能会更严重一样(我在笔记本的节能模式下测试过,会很明显)

start: function() {
        var _this = this;
        fill_ = setInterval(function() {
        _this.datas.push("Test String " + _this.datas.length);
        console.log(_this.datas.length);
      }, 51);
    },
8

@EveLuty 十分神奇,进入和退出不同居然能解决我遇到的问题hhh 不过这仍然是一个问题啊,为何相同就会导致问题,哪怕进入和退出的时候都是0 所以这个issue还是暂时先继续开着好了

4

好吧,这个问题其实我一直都知道,原因其实比较坑爹,和浏览器的 timer / 渲染机制有关系。举例来说:

  1. 元素被插入 DOM,此时 opacity 为 0,然后下一帧 enter class 被移除,触发 CSS transition; 但是这个 transition 要等到下一个浏览器的渲染 tick(也就是 requestAnimationFrame 的时候)才启动,在那之前,元素的 opacity 依然为 0
  2. 假如在 enter class 被移除后,CSS transition 启动之前的极小间隔触发数组更新,则浏览器会判断 opacity 的当前值和目标值相等,于是直接跳过了 transition,不触发 transitionend 事件... 没有这个事件,Vue 就不知道何时调用 afterLeave 了。

这就是为什么 enter 和 leave 用不同的值可以解决这个问题。

至于能不能在 Vue 的机制里解决这个问题,还要再看看。

1

好吧hhh我仔细考虑过后觉得也是这个问题,还是谢谢各位帮忙了~

Evan You notifications@github.com于2016年3月25日周五 上午10:54写道:

6

抱歉再次开启此issue。。 这个问题在很大程度上已经解决了,但是实际应用中还是会不定期出现残留,想要reproduction的话恐怕比较困难了。。。 希望能够在文档里引用一下 @EveLuty 的解决方案,谢谢

9

@ClassicOldSong 应该是在不支持 rAF 的旧浏览器吧... 这个真没办法

7

@yyx990803 并不是。。。在最新的Chrome下也出现了

3

@ClassicOldSong 这... 那保险起见只能用 0.00001 大法了