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节点,提升性能