Vue2使用Vue.compile动态更新模板时beforeDestroy钩子获取错误组件标识的问题
哥们,我太懂你踩的这个坑了!这其实是Vue2虚拟DOM diff算法的特性搞的鬼,咱们一步步捋清楚:
问题根源:Vue2的就地复用策略
Vue2为了提升渲染性能,对同类型的组件/元素会采用就地复用的diff逻辑。拿你的场景举例:
- 旧模板里有两个并排的
CustomComponent,顺序是dummy-id='one'(索引0)、dummy-id='two'(索引1) - 新模板里只剩
dummy-id='two'(索引0)
这时候Vue会对比相同索引的节点:发现索引0的节点都是CustomComponent类型,就直接复用原来索引0的组件实例(也就是dummy-id='one'那个),然后把它的dummy-id更新成two;同时,因为新模板里没有索引1的节点了,Vue会销毁原来索引1的实例(也就是dummy-id='two'那个)。
所以你在beforeDestroy里拿到的是原来的two组件,而不是被移除的one——这就是为啥标识和_uuid都不对!之前没触发这个问题,是因为你没用到beforeDestroy钩子,组件实例被复用的细节没暴露出来而已。
解决方案:给动态组件加唯一key
解决这个问题的核心就是让Vue能精准识别每个组件的唯一性,不要随便复用实例。只需要给你的CustomComponent加上唯一的key属性就行:
比如把你的模板字符串改成这样:
<!-- templateStringOne --> <div> <CustomComponent dummy-id='one' :key="'one'"/> <CustomComponent dummy-id='two' :key="'two'"/> </div> <!-- templateStringTwo --> <div> <CustomComponent dummy-id='two' :key="'two'"/> </div>
这里用dummy-id作为key的取值就很合适,因为它本身就是每个组件的唯一标识。加上key之后,Vue的diff算法会通过key来匹配新旧节点:它会发现新模板里没有key='one'的节点,就会正确销毁dummy-id='one'的组件;而key='two'的节点存在,就保留对应的实例,不会再复用旧的one实例。
这时候你再看beforeDestroy的日志,就能拿到正确的dummy-id='one'和对应的_uuid了!
额外提醒
Vue2的这个diff特性是一把双刃剑:日常场景下的就地复用能提升性能,但在动态增减同类型组件/元素的场景下,必须加key来避免实例复用导致的逻辑混乱。你之前用了两年没出问题,只是因为业务逻辑没涉及到监听组件销毁的场景,现在用beforeDestroy就触发了这个隐藏的细节。
备注:内容来源于stack exchange,提问作者gerrul3r

