vue中的key

6,009 阅读5分钟

前言

题目来源:juejin.cn/post/684490…

目标:通过面试题来让自己的知识面更加广,更加深入。

(滴滴、饿了么)写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key关键词是什么

首先我们从官网的解释出发:

划重点

  • key会用在虚拟DOM算法(diff算法)中,用来辨别新旧节点。
  • 不带key的时候会最大限度减少元素的变动,尽可能用相同元素。(就地复用)
  • 带key的时候,会基于相同的key来进行排列。(相同的复用)
  • 带key还能触发过渡效果,以及触发组件的生命周期

上面这么4点就是官方文档中主要解释的意思。吃透了以上几点,也就知道了key是什么以及他的作用。

从源码层面来窥探key在diff算法中的作用

  • 判断是否是相同节点的时候

diff算法我们知道,他会判断开始和结尾4个节点是否是相同的节点,而这个sameVnode第一步就是判断两个元素的key值。意味着key相同,它们才有可能是相同元素,不相同,直接返回false。

  • 判断是否是相同的静态节点的时候

判断静态节点也是和上述流程一致的。

  • 创建key-index的map的时候

这边我只是截取了代码片段,这一步是将vnode的子元素数组,生成key-index的map,方便通过key去查找对应的index子元素,进行相同节点的比较。而它的上文是前后4个新旧节点两两对比不成功的情况下,下文就是通过这个map去定位相同的元素。

带key和不带key,谁的速度更快

聊到速度这个话题,我们先可以从本质上比较一下。

我们用简单的数据渲染的例子来看一下

  • 不带key的时候

实例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <div @click="changeData">切换数据</div>
        <li v-for="item in list">{{ item.text }}</li>     
    </div>
    <script src="../../dist/vue.min.js"></script>
    <script type="module" crossorigin=“use-credentials”>
        var app = new Vue({
            el: '#app',
            data: {
                list: [],
                list1: [],
                list2: [],
                nowDate: '',
                updateTime: '',
            },
            created() {
                for (let i = 0; i <= 100000; i++) {
                    this.list1.push({
                        id: i,
                        text: i
                    })
                    this.list2.push({
                        id: i * 2,
                        text: 100000 - i
                    })
                }
                this.list = this.list1
            },
            methods: {
                changeData() {
                    this.nowDate = Date.now();
                    this.list = this.list2;
                }
            },
            updated() {
                const date = Date.now();
                console.log(`updateTime: ${date - this.nowDate}`);
            }
        })
    </script>
</body>
</html>

  • 带key的时候

将上述遍历的html代码带上key值。

    <li v-for="item in list" :key="item.id">{{ item.text }}</li>     

总结: 不带key的速度是更快的。原因有如下:在上述例子中,不带key的省略了销毁和创建dom的开销,只需要替换文本节点就ok了,而带key的却需要进行patch流程,而且需要把能复用的那部分元素找出来,将不能复用的消除,并且重新创建新的dom元素。

不带key会有什么弊病

既然我们从上面检测出来,是不带key的会更快一点,那么我们为什么要带key呢?

1、不带key本身来说没问题,但是当你遍历的元素中,有组件的时候,就会发现问题。因为组件是可以带有自身状态的。我们来看一下下面这个例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <div  v-for="(item, index) in list">
            <component-a></component-a>     
            <button @click="deleteInput(index)">删除</button>
        </div>
    </div>
    <script src="../../dist/vue.min.js"></script>
    <script type="module" crossorigin=“use-credentials”>
        var ComponentA = {
            data: function () {
                return {
                    input: 0
                }
            },
            template: '<input v-model="input" />'
        }
        var app = new Vue({
            el: '#app',
            data: {
                list: ['1', '2', '3'],
            },
            methods: {
                changeData() {
                    this.nowDate = Date.now();
                    this.list = this.list2;
                },
                deleteInput(index) {
                    this.list.splice(index, 1);
                }
            },
            components: {
                ComponentA
            }
        })
    </script>
</body>
</html>

上述代码很简单,主要实现一下功能:

1、组件携带有自己状态的input
2、页面遍历子组件生成3个input

然后点击中间那个删除,生成的结果:

期望的结果:

大家可以绑key之后试一下,就是我们期望的结果。

2、带key的因为不是就地复用,所以可以实现一些过渡的动画效果。

建议不要用index值去作为key的值

平时我们很喜欢用index去作为key的索引,但是这样的话,key值就失去了本身的意义,在遇到上述那几个例子的时候,同样回去踩坑。

很多集团项目中,会用到eslint去检测template中v-for是否用了key值。从而减少错误率。那么key最好和数据返回的id值去绑定。

总结

这个知识点其实很浅,但是也改变了我的很多认知。

1、不一定很多时候都要写key值,如果是简单的列表渲染,我觉得大可不必。

2、当你要去写key值的时候,最好不要用index,因为index这个值和就地复用没啥区别。

3、当你要实现一些过渡效果的时候,不妨看一下vue的文档,有一些现成的过渡效果可以直接用。