关于Vue Diff算法 -- Duplicate keys导致update时报错

2,498 阅读2分钟

最近遇到的一个问题

平时在使用重复key的时候,会经常遇到报错

Duplicate keys detected: 'xxxxxx'. This may cause an update error. found in xxxxx

以前项目中在后端数据有重复数据的时候经常会遇到这个warning,这个一般只是一个警告,不会导致运行时报错。但最近在项目中由于Duplicate keys 导致update时报错,看了一下源码,研究出了其中的原因。

这是一段会报错的代码

<template>
  <div>
  <el-checkbox v-model="checkAll" @change="handleCheckAllChange">改变第一个和最后一个l对象的key和value</el-checkbox>
      <ul>
          <li v-for="city in childList"  :key="city.code">{{city.label}}</li>
      </ul>
  </div>
</template>
<script>

    export default {
        data() {
            return {
                childList :[
                    {label:"原来的标签1",code:"label1",value:true},
                    {label:"原来的标签2",code:"label2",value:true},
                    {label:"原来的标签3",code:"label3",value:true},
                    {label:"原来的标签4",code:"label3",value:true},
                    {label:"原来的标签5",code:"label4",value:true},
                    {label:"原来的标签6",code:"label6",value:true},
            ]
            };
        },
        methods: {
            changeChildList(){
                this.childList=[
                    {label:"修改的标签1",code:"labelupdate1",value:true},
                    {label:"原来的标签2",code:"label2",value:true},
                    {label:"原来的标签3",code:"label3",value:true},
                    {label:"原来的标签4",code:"label3",value:true},
                    {label:"原来的标签5",code:"label4",value:true},
                    {label:"修改的标签6",code:"labelupdate6",value:true},
                ]
            },
            handleCheckAllChange(val) {
                this.changeChildList()
            }
        }
    };
</script>

**报错的原因如下, 首先,报错的地方是在function sameVnode内

function sameVnode (a, b) {
    return (
        a.key === b.key && ( //这里报了错,a是undefined,也就是说第一个参数找不到了
            (
                a.tag === b.tag &&
                a.isComment === b.isComment &&
                isDef(a.data) === isDef(b.data) &&
                sameInputType(a, b)
            ) || (
                isTrue(a.isAsyncPlaceholder) &&
                a.asyncFactory === b.asyncFactory &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}

注释一下报错的原因

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
   //其他代码先忽略,因为本例子中是list的第一个元素和最后元素改变了,且不是第一次渲染
   //所以前面的6种比较情况分别是,
   // 1.oldstart vs newstart,2.oldstart vs newend 3.oldend vs newstart,2.oldend vs newend      5.oldStartVnode isundefined 6。oldEndVnode is undefined
   //上面的均不执行,直接执行else,也就是下面所展示的代码
   //首先我们这个例子是定义了key
   //如果有定义key的时候,则直接对比key,在oldKeyToIdx找到vnode
   //如果key相同(即使其他属性不一样),则认为在oldKeyToIdx是同一个vnode
   //所以当重复key的话,idxInOld还是返回和之前的key同一个的idxInOld
   //但是用oldCh[idxInOld]去到的vnode,因为在前一个相同key的vnode已经取到了oldCh[idxInOld]并将oldCh[idxInOld];设为unde
   //所以第二个重复key取值的时候,能找到idxInOld
   //但是vnodeToMove =oldCh[idxInOld],vnodeToMove就只能取到undefined了
   //也就导致了 if (sameVnode(vnodeToMove, newStartVnode)){}
   //里的第一个参数为undefined,报错就抛出了Cannot read property 'key' of undefined

idxInOld = isDef(newStartVnode.key)
                ? oldKeyToIdx[newStartVnode.key]//本例子有定义key
                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
                if (isUndef(idxInOld)) { 
                //如果在oldKeyToIdx或oldKeyToIdx都找不到这idx,说明是新增vnode,直接创建
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
                } else {
                //否则说明这个vnode在oldCh里面存在,直接复用这个vnode
                vnodeToMove = oldCh[idxInOld];//这里是undefine,所以导致下面的sameVnode的第一个参数是undefined,导致报错
                if (sameVnode(vnodeToMove, newStartVnode)) {//这里
                        ···
                     //找到这个vnode复用后把它从oldCh里面删掉
                    oldCh[idxInOld] = undefined;
                    }
}

可能下面这张图也许大概应该会相对清楚一点

6NYv3n.png

这篇文章主要作为我学习vue源码的一个记录,如果有不妥当的地方,请各位帮忙指出,多多指教,谢谢你们。