当前位置:   article > 正文

for vue 一行2列_Vue中的v-for踩坑之旅(继上一章key的案例)

vue中循环显示一个数组,一行显示两个数据
342effd3ec386d6ffdaa217e7e4baaf3.png

用过Vue的同学都知道,v-for指令常用于遍历数组或者对象,然后依次渲染出指定的内容。同时,我们也知道,官方文档也建议,在使用v-for指令时,记得要加上key属性,方便提升应用性能。例如一个简单的增删Todo应用如下所示:

ee67c6a5ca45b5b094fbe67e22c024e6.png

代码很简单明了,也运行的很高效。我们用了v-for指令,也加了key, 一切都和完美,感叹Vue真好用,真是高效哇!

组件封装

在Vue中,官方建议我们多进行组件封装和抽象,这样方便后期维护。因为每一个Todo都有自己的状态,例如完成或者未完成, 我们需要将每一个Todo抽象为组件。所以我们要做一下简单的改进:新建一个TodoItem.vue,然后在主文件中导入使用

f3ed38c3dba8d898c0d0a88647027d97.png

代码非常简单,我们在TodoItem.vue中新增加了一个checked属性,当复选框勾选后,todo文字会显示删除效果。重新修改下主文件

18e4c4c57f74e670df4517c837d51a74.png

代码变化不大,只是在v-for循环时加入了而已。看看运行效果

65696199b02a013eaaf9fe37af095c0a.png

好像没啥问题? 但是如果将第一条记录勾选后然后再删除,令人费解的事情发生了:

0d9b5fbaef010cc96762183ca7b9403a.png

可以清晰地看到,第二条记录之前是未勾选状态,但是删除第一条后,它变成了勾选状态?这是为什么呢?

问题分析

这个问题其实我现实项目中的一个抽象,当时我也遇到了类似的问题,想了好几个小时都没解决。我一行一行分析我的代码,是不是代码哪里写错了?最后一行一行分析,突然想到是不是key用的不对?于是我将key弄成一个唯一的id,然后奇迹发生了,页面都正常了。这是为什么呢?

在我们的例子,如果我们我们将v-for中的key改成如下所示(保证todo不重复)问题就解决了:

fc5afc1449d479b31b0e24b69ca8c63f.png

虽然当时问题是解决了,但是这个v-for的问题一直在困扰我,到底是什么原因导致这种现象发生,为什么key弄成唯一的id就好使了呢?官方说在v-for时增加key可以提升应用性能,到底是怎么提升的?

刚好最近在看Vue Virtual DOM的diff算法,终于从中间找到了解决该问题的曙光

Vue中的Virtual DOM

在Vue中,template中的内容最后都会被解析并渲染为VNode, 这个就是所谓的Virtual DOM。当我们修改Vue中的数据后,Vue会对前后两次的VNode进行diff,找出最小的差异,然后再渲染DOM,这样可以提高应用的性能。

VNode其实是对真实DOM的Javascript抽象,例如一个简答的DOM树如下所示:

f1901cadc9186f6f96c3fc47a9588937.png

也就是说,VNode可以真实描述并还原DOM。

Virtual DOM diff算法

我们先看看diff的核心函数(这些是源码的抽象,源码里面更为复杂):

4619f348d8d85e7720d7f15c1059544e.png

patch就是比较前后两个VNode,然后找出其最小差异并修改、创建或者删除DOM。在该函数中,oldVNode代表旧的数据,vnode代表最新的数据。比较时,会进行深度优先逐层进行比较。如下图所示:

b9fd3e7ddee92bb09f0736528c88df0f.png

也就是说,在上图中,只有相同颜色的VNode才进行比较,这样算法复杂度就比较低,整体下来只有O(n),效率算法非常高了。

从patch函数可以看出,diff算法的的核心逻辑是这样的

  • 如果旧的VNode不存在,新的VNode存在,则创建新的DOM
  • 如果旧的VNode存在,新的VNode不存在,则删除旧的DOM
  • 如果新旧两个VNode都存在并相同,则找出最小差异然后更新DOM
  • 如果新旧两个VNode都存但不相同,则将旧的DOM删除,然后创建新的DOM

这里的关键是:如何判断两个VNode相同呢?请看下面的代码:

ed2f5ac2f94b48311a61db07853b47fc.png

也就是说,只有当 key、 tag、 isComment(是否为注释节点)相同、 data同时定义(或不定义),同时满足当标签类型为input的时候type相同,那么它们就是相同的VNode。

注意这里的key相同,才代表VNode相同。对比我们之前出错的样例,因为我们的key是索引号,可知第一条记录的索引号为0。当第一条记录被删除后,第二条记录的key的索引号会从1变为0,这样导致了两者的key相同。因为key相同时,diff算法会认为它们是相同的VNode,那么旧的VNode(如果VNode是一个组件,它有一个componentInstance指向Vue实例)指向的Vue实例会被复用,导致显示出错。修改key为唯一id时,根据上文patch函数的逻辑,旧的VNode所对应的DOM会被干掉,然后得新的DOM会被创建。因为是新创建的DOM,那么对应的Vue也是新创建的,一切就会显示正常。

所以,保证key唯一,就可以解决组件出错的问题

上文中,我们没有提到diff算法的核心,也就是说当两个VNode相同时,patchVnode是怎么实现的。建议大家阅读相关参考文章。

总结

v-for使用非常简单,但是要特别注意key的使用。官方之所以说加上key会提升应用性能是因为:key相同时,两个VNode会相同,可以避免不必要的DOM更新。而且在diff内部,也会根据key来跟踪VNode。但是,官方也说了,尽量保证key是唯一的id,这样可以避免一些匪夷所思的bug。

参考资料

  • VirtualDOM与diff(Vue实现).MarkDown.MarkDown)
  • 数据状态更新时的差异 diff 及 patch 机制
  • 解析vue2.0的diff算法
  • 详解vue的diff算法
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/76778
推荐阅读
相关标签
  

闽ICP备14008679号