[ // 第一个被删了 { tag: "li", key: 0, // 这里其实上一轮子组件对应的是第二个 假设子组件的text是2 }, { tag: "li", key: 1, // 这里其实子组件对应的是第三个 假设子组件的text是3 }, ];
虽然在注释里我们自己清楚的知道,第一个 vnode 被删除了,但是对于 Vue 来说,它是感知不到子组件里面到底是什么样的实现(它不会深入子组件去对比文本内容),那么这时候 Vue 会怎么 patch 呢?
由于对应的 key使用了 index导致的错乱,它会把
原来的第一个节点text: 1直接复用。
原来的第二个节点text: 2直接复用。
然后发现新节点里少了一个,直接把多出来的第三个节点text: 3 丢掉。
至此为止,我们本应该把 text: 1节点删掉,然后text: 2、text: 3 节点复用,就变成了错误的把 text: 3 节点给删掉了。
为什么不要用随机数作为key?
<item :key="Math.random()" v-for="(num, index) in nums" :num="num" :class="`item${num}`" />
其实我听过一种说法,既然官方要求一个 唯一的key,是不是可以用 Math.random() 作为 key 来偷懒?这是一个很鸡贼的想法,看看会发生什么吧。
首先 oldVnode 是这样的:
[ { tag: "item", key: 0.6330715699108844, props: { num: 1 } }, { tag: "item", key: 0.25104533240710514, props: { num: 2 } }, { tag: "item", key: 0.4114769152411637, props: { num: 3 } } ];
更新以后是:
[ { tag: "item", + key: 0.11046018699748683, props: { + num: 3 } }, { tag: "item", + key: 0.8549799545696619, props: { + num: 2 } }, { tag: "item", + key: 0.18674467938937478, props: { + num: 1 } } ];
可以看到,key 变成了完全全新的 3 个随机数。
上面说到,diff 子节点的首尾对比如果都没有命中,就会进入 key 的详细对比过程,简单来说,就是利用旧节点的 key -> index 的关系建立一个 map 映射表,然后用新节点的 key 去匹配,如果没找到的话,就会调用 createElm 方法 重新建立 一个新节点。
具体代码在这:
// 建立旧节点的 key -> index 映射表 oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); // 去映射表里找可以复用的 index idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); // 一定是找不到的,因为新节点的 key 是随机生成的。 if (isUndef(idxInOld)) { // 完全通过 vnode 新建一个真实的子节点 createElm(); }
也就是说,咱们的这个更新过程可以这样描述:
123 -> 前面重新创建三个子组件 -> 321123 -> 删除、销毁后面三个子组件 -> 321。
发现问题了吧?这是毁灭性的灾难,创建新的组件和销毁组件的成本你们晓得的伐……本来仅仅是对组件移动位置就可以完成的更新,被我们毁成这样了。
总结
经过这样的一段旅行,diff 这个庞大的过程就结束了。
我们收获了什么?
用组件唯一的 id(一般由后端返回)作为它的 key,实在没有的情况下,可以在获取到列表的时候通过某种规则为它们创建一个 key,并保证这个 key 在组件整个生命周期中都保持稳定。
如果你的列表顺序会改变,别用 index 作为 key,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index 都是 0, 1, 2 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。列表顺序不变也尽量别用,可能会误导新人。
千万别用随机数作为 key,不然旧节点会被全部删掉,新节点重新创建,你的老板会被你气死。
到此这篇关于详解为什么Vue中不要用index作为key(diff算法)的文章就介绍到这了,更多相关Vue不要用index作为key内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章: