Vue的v-for为什么要加key

5,395 阅读3分钟

v-for为什么要加key

问题:在原有的数组中用unshift()插入一条新的数据,这个时候:key='id'、:key='index'和不绑定key的渲染开销谁更大?

虚拟dom(virtual dom)

要理解diff的过程,先要对virtual dom有个了解,这里简单介绍下。

【作用】 我们都知道重绘和回流,回流会导致dom重新渲染,比较耗性能;而virtual dom就是用一个对象去代替dom对象,当有多次更新dom的动作时,不会立即更新dom,而是将变化保存到一个对象中,最终一次性将改变渲染出来

【形式】

<div>
    <p>
       span
       span
    </p>
    <span></span>
</div>

以上代码转换成virtual dom就是如下形式(当然省去了很多其他属性)

{
    tag: 'div',
    children: [
        {
            tag: 'p',
            children:[
                {
                    tag:'span'
                },
                {
                    tag:'span
                }
            ]
        },
        {
            tag: 'span'
        }
    ]
}

diff原理

图中很清楚的说明了,diff的比较过程只会在同层级比较,不会跨级比较。 整体的比较过程可以理解为一个层层递进的过程,每一级都是一个vnode数组,当比较其中一个vnode时,若children不一样,就会进入updateChildren函数(其主要入参为newChildren和oldChildren,此时newChildren和oldChildren为同级的vnode数组);然后逐一比较children里的节点,对于children的children,再循环以上步骤。 updateChildren就是diff最核心的算法。

为什么要加key

源码中有这么一段话,简略了代码,有一个sameVnode函数,其源码简化如下:

function sameVnode (a, b) {
  return (
    a.key === b.key && a.tag === b.tag
  )
}

就地复用

也就是说,判断两个节点是否为同一个节点(也就是是否可复用),标准是key相同且tag相同。以下图的改变为例

下面以数据为例

const list = [
    {
        id:3,
        name:'C'
    },
    {
        id:2,
        name:'B'
    },
    {
        id:1,
        name:'A'
    }
]
<div v-for="(item, index) in list" :key="index" >{{item.name}}</div>

在第一条数据前添加一条数据

const list = [
    {
      	id:4,
      	name:'D'
    },
    {
        id:3,
        name:'C'
    },
    {
        id:2,
        name:'B'
    },
    {
        id:1,
        name:'A'
    }
]
  • 当不加key时,key都是undefined,默认相同,此时也会按照diff算法的就地复用来进行比较

看图:

就地复用

性能消耗小

以上,D复用C,C复用B,B复用A,添加了A。 4次渲染

	之前的数据                              			之后的数据

key:undefined index: 0 name:'C'		key:undefined index:0 name:'D'
key:undefined index: 1 name:'B'		key:undefined index:1 name:'C'
key:undefined index: 2 name:'A'		key:undefined index:2 name:'B'
					key:undefined index:3 name:'A'
  • 更新渲染数据,通过index定义的key去进行前后数据的对比 4次渲染
	之前的数据                              			之后的数据

key:0 index: 0 name:'C'				key:0 index:0 name:'D'
key:1 index: 1 name:'B'				key:1 index:1 name:'C'
key:2 index: 2 name:'A'				key:2 index:2 name:'B'
						key:3 index:3 name:'A'

通过上面清晰的比较。四条数据都必须重新渲染。

注意:比较的是key

  • 更新渲染数据,通过id定义的key来看进行前后数据的对比

看图:

	之前的数据                              			之后的数据

key:3 index: 0 name:'王五'				key:4 index:0 name:'赵六'
key:2 index: 1 name:'李四'				key:3 index:1 name:'王五'
key:1 index: 2 name:'赵六'				key:2 index:2 name:'李四'
										  key:1 index:3 name:'张三'

前后对比之后,发现后三条数据复用了,只有一条数据需要更新渲染

为什么不建议用index作为key

在工作中,发现很多人直接用index作为key,好像几乎没遇到过什么问题。确实,index作为key,在表现上确实几乎没有问题。 查看当前连接,下载demo,改变index.html中的绑定的key为索引、不绑定key、key为id,查看效果。

总结

  • diff算法默认使用“就地复用”的策略,是一个首尾交叉对比的过程。

  • 不要使用index作为key,如果不加key,默认采用“就地复用”的策略,推荐用id作为key

  • “就地复用”的策略,只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

  • 将与元素唯一对应的值作为key,可以最大化利用dom节点,提升性能