Vue v-for中为什么要加属性key

在Vue官网中强烈推荐在使用v-for时要加key属性,这篇文章看看加属性key和不加属性key的区别。我们模拟一下Vue中对这部分的处理流程。先创建3个div元素,内容为

xiaozhang

xiaowang

xiaoliu

然后在第一个div元素之前再添加一个新div元素xiaoli,渲染后的内容如下:

xiaoli

xiaozhang

xiaowang

xiaoliu

我们用一个对象数组来模拟这一过程,对象数组如下:

        //带key数据
        var oldList = [{ key : 10, name : "xiaozhang", tag : "div"},
                       { key : 11, name : "xiaowang", tag : "div"},
                       { key : 12, name : "xiaoliu", tag : "div"}]

        var newList = [{ key : 9,  name : "xiaoli", tag : "div"},
                       { key : 10, name : "xiaozhang", tag : "div"},
                       { key : 11, name : "xiaowang", tag : "div"},
                       { key : 12, name : "xiaoliu", tag : "div"}]
        //不带key数据
        var oldList_ = [{ name : "xiaozhang", tag : "div"},
                       { name : "xiaowang", tag : "div"},
                       { name : "xiaoliu", tag : "div"}]

        var newList_ = [{ name : "xiaoli", tag : "div"},
                       { name : "xiaozhang", tag : "div"},
                       { name : "xiaowang", tag : "div"},
                       { name : "xiaoliu", tag : "div"}]

带key的数据里面对象有key属性,模拟使用v-for时带key属性(:key="item.key")。

首先我们用oldList创建3个div元素,并插入到父节点中,然后用newList来更新这3个div元素,并插入新元素"xiaoli"。代码如下:

<html>

<head>
    <style type="text/css">

    </style>
</head>

<body>


    <div id="app">

    </div>
    <script>

        //带key数据
        var oldList = [{ key : 10, name : "xiaozhang", tag : "div"},
                       { key : 11, name : "xiaowang", tag : "div"},
                       { key : 12, name : "xiaoliu", tag : "div"}]

        var newList = [{ key : 9,  name : "xiaoli", tag : "div"},
                       { key : 10, name : "xiaozhang", tag : "div"},
                       { key : 11, name : "xiaowang", tag : "div"},
                       { key : 12, name : "xiaoliu", tag : "div"}]
        //不带key数据
        var oldList_ = [{ name : "xiaozhang", tag : "div"},
                       { name : "xiaowang", tag : "div"},
                       { name : "xiaoliu", tag : "div"}]

        var newList_ = [{ name : "xiaoli", tag : "div"},
                       { name : "xiaozhang", tag : "div"},
                       { name : "xiaowang", tag : "div"},
                       { name : "xiaoliu", tag : "div"}]

        //首先创建3个div元素
        function createDom(parentElm, list){
            var dom3 = document.createElement(list[2].tag);
            dom3.textContent = list[2].name;
            parentElm.appendChild(dom3);

            var dom2 = document.createElement(list[1].tag);
            dom2.textContent = list[1].name;
            parentElm.insertBefore(dom2, dom3);

            var dom1 = document.createElement(list[0].tag);
            dom1.textContent = list[0].name;
            parentElm.insertBefore(dom1, dom2);
        }
 
        // createDom(document.getElementById("app"), oldList_); //不带key
         createDom(document.getElementById("app"), oldList);  //带key

        function isSameVNode(oldNode, newNode){
            //根据key判断新旧两个节点是否相同
            return (oldNode.key === newNode.key) && (oldNode.tag === newNode.tag);
        }

        //更新节点
        function updateDom(parentElm, oldList, newList){
            var oldStartIdx = 0, newStartIdx = 0;
            var oldEndIdx = oldList.length - 1, newEndIdx = newList.length - 1;
            var oldStartVNode = oldList[0], newStartVNode = newList[0];
            var oldEndVNode = oldList[oldEndIdx], newEndVNode = newList[newEndIdx];

            while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
                if(isSameVNode(oldStartVNode, newStartVNode)){
                    //从新旧节点列表的开始部分开始遍历,不带key的节点走这个分支,因为不带key,所以key都是undefined
                    if(oldStartVNode.name !== newStartVNode.name){
                        console.log("update dom"); //新旧节点内容不同,更新dom中的内容
                        parentElm.children[newStartIdx].textContent = newStartVNode.name;
                    }
                    oldStartVNode = oldList[++oldStartIdx];
                    newStartVNode = newList[++newStartIdx];
                }else if(isSameVNode(oldEndVNode, newEndVNode)){
                    //从新旧节点列表的结尾部分开始遍历,带key的节点走这个分支
                    if(oldEndVNode.name !== newEndVNode.name){
                        //从结尾部分遍历时,前三个dom元素内容都相同,所以不会走这里,也就没有操作dom,
                        parentElm.children[newEndIdx].textContent = newEndVNode.name;
                    }
                    oldEndVNode = oldList[--oldEndIdx];
                    newEndVNode = newList[--newEndIdx];
                }
            }
    
            if (oldStartIdx > oldEndIdx) {
                //遍历完后,新节点比旧节点多,所以要增加节点
                if(newList[newEndIdx + 1] !== undefined){
                    //带key的走这个分支,在3个子节点的前面插入一个元素"xiaoli"
                    var dom0 = document.createElement(newList[0].tag);
                    dom0.textContent = newList[0].name;
                    parentElm.insertBefore(dom0, parentElm.children[newEndIdx]);
                }else{
                    //不带key的走这个分支,在3个子节点最后插入一个元素“xiaoliu"
                    var dom3 = document.createElement(newList[newEndIdx].tag);
                    dom3.textContent = newList[newEndIdx].name;
                    parentElm.appendChild(dom3);
                }
            }
        }

        setTimeout(() => {
            //更新dom
            // updateDom(document.getElementById("app"), oldList_, newList_); //不带key
             updateDom(document.getElementById("app"), oldList, newList); //带key
        }, 2000);
    </script>

</body>

</html>

在代码中我们进行了详细的注释,如哪里有不明白的地方可以单步调试一下。

当带key时,从对象数组的最后一个元素开始遍历,由于元素内容textContent相同所以不用操作dom,复用之前的dom元素,什么也不做,遍历下一个元素。最后在最前面插入新添加的元素即可。如下图所示:

当不带key时,从对象数组的第一个元素开始遍历,由于旧对象数组的第一个元素(xiaozhang)和新对象数组的第一个对象元素不同(xiaoli),所以要操作dom,把textContent改为"xiaoli",新旧数组的第二个元素,和第三个元素处理方式和第一个元素相同,都需要操作dom,最后还要插入第四个元素“xiaoliu”。如下图所示:

发布了180 篇原创文章 · 获赞 16 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/liubangbo/article/details/104225414