key 的一个错误使用——使用 index 作为 key
不知道你在写 v-for 的时候,会不会直接使用 index 作为它的 key 值,是的,我承认我会,不得不说,这真的不是一个好习惯。
以下是核心代码,其中 arrData 的值为 [1,2,3,4]
当然一开始的时候,数据渲染肯定是没有问题的
好了,我们来看下以下的操作:
可以看到,我将3拖到2上面的时候,下面的数据变成了 1342,但是上面视图的还是1234。然后我第四位置拖到第三位置的时候,下面的数据也是生效的,但是上面的数据似乎全部错乱了。很好,我们重现了案发现场。
接着我改了绑定的 key 值,因为这里的例子比较特殊,我们就认为 item 的值都不相同
再看效果:
是的,这个时候数据就完全跟视图同步了。
为什么?
先看官方文档中 key 的一句介绍
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
之所以会造成上面渲染错误的情况,是因为我们的 key 值不是独特的,比如上面的 key 值,在调整数组顺序后就每一项原来的 key 值都变了,所以导致了渲染错误。
我们先来得出一个结论,用 index 作为 key 值是有隐患的,除非你能保证 index 始终能够能够作为一个唯一的标识
key 值到底有什么用
在 vue2.0 之后,我们不写 key 的话,就会报 warning,那也就是说官方是希望我们写 key 值的,那么 key 到底在 vue 中扮演了什么样的角色?
不使用 key 可以提高性能么
答案是,是的!可以!
先看官方解释:
如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
比如现在有一个数组 [1,2,3,4]变成了[2,1,3,4],那么没有 key 的值会采取一种“就地更新策略”,见下图。它不会移动元素节点的位置,而是直接修改元素本身,这样就节省了一部分性能
而对于有 key 值的元素,它的更新方式如下图所示。可以看到,这里它对 DOM 是移除/添加的操作,这是比较耗性能的。
竟然不带 key 性能更优,为何还要带 key
先来看一个例子,核心代码如下,这里模仿一个切换 tab 的功能,也就是切换的tab1 是1,2,3,4。tab2 是 5,6,7,8。其中有设置了一个点击设置第一项字体色为红色的功能。
那么当我们点击tab1将字体色设置成红色之后,再切换到 tab2,我们预期的结果是我们第一项字体的初始颜色而不是红色,但是结果却还是红色。
这就超出了我们的预期了,也就是官方文档所说的,默认模式指的就是不带 key 的状态,对于依赖于子组件状态或者临时 DOM 状态的,这种模式是不适用的。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
我们来看带上 key 之后的效果
这就是官方文档之所以推荐我们写 key 的原因,根据文档的介绍,如下:
使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景的时候它可能会很有用:
完整地触发组件的生命周期钩子
触发过渡
那么 Vue 底层 key 值到底是怎么去做到以上的功能?我们就得聊聊 diff 算法以及虚拟 DOM 了。
key 在 diff 算法中的作用
这里我们不谈 diff 算法的具体,只看 key 值在其中的作用。(diff 算法有机会我们再聊)
看 vue 源码中 ``
我们整理一下代码块:
那么最主要还是 createKeyToOldIdx 和 findIdxInOld 两个函数的比较,那么他们做了什么呢?
我们可以看到,如果我们有 key 值,我们就可以直接在 createKeyToOldIdx 方法中创建的 map 对象中根据我们的 key 值,直接找到相应的值。没有 key 值,则需要遍历才能拿到。相比于遍历,映射的速度会更快。
key 值是每一个 vnode 的唯一标识,依靠 key,我们可以更快的拿到 oldVnode 中相对应的节点。