# 背景
在我们写 v-for
列表渲染时,如果没有给要被循环渲染的元素添加 key
的话是会报 warning
。为什么会会这样,背后到底有什么的逻辑联系?
我们来举个栗子,代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>demo5</title>
<script src="../lib/vue.js"></script>
</head>
<body>
<div id="app">
<li v-for="item in list">{{ item }}</li>
</div>
<script>
const curVue = new Vue({
data: {
list: ['a', 'b', 'c', 'd', 'e']
},
mounted () {
setTimeout(() => {
this.list.splice(2, 0, 'f')
}, 1000)
},
}).$mount('#app')
console.log(curVue)
console.log(curVue.$options.render.toString())
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 不写 key 和 写 key 的区别
如果不写 key
的话,在 patch
的时候,sameVnode
方法会返回 true
,会执行 patchVnode
,复用当前的节点,更新 DOM
数据,流程如下:
在没有设置 key
的情况下,流程如下:
- 比较
a
,a
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 比较
b
,b
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 比较
c
,f
,不相同类型的节点,进行patchVnode
,数据不同,发生DOM
操作 - 比较
d
,c
,不相同类型的节点,进行patchVnode
,数据不同,发生DOM
操作 - 比较
e
,d
,不相同类型的节点,进行patchVnode
,数据不同,发生DOM
操作 - 循环结束,将
e
插入到DOM
中
有 3 次更新操作,1次插入操作
我们再来看看,在有设置 key
的情况下:
- 比较
a
,a
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 比较
b
,b
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 比较
c
,f
,不相同类型的节点- 比较
e
,e
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作
- 比较
- 比较
d
,d
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 比较
c
,c
,相同类型的节点,进行patchVnode
,但数据相同,不发生DOM
操作 - 循环结束,将
f
插入到c
之前
只发生了 1 次插入操作
在这上述情况下,设置 key
值显然是提高 diff
的效率,而且减少了不必要的 DOM
,但是如果上述在在末尾插入的,设置也没有什么卵用了。
- ,这种把 `index` 作为 `key` 显然也没有什么卵用。
- 我们想象一种极端的情况,比如在列表中嵌套许多的子节点,如果有 curd 相关的操作,设置 key 的唯一值,对性能的提升是巨大的。
# 原理分析
我们可以简单看一下源码:
主要是在 sameVnode
这个方法中,这里判断是否为同一个key
,首先判断的是key
值是否相等如果没有设置key
,那么key
为undefined
,这时候undefined
是恒等于undefined
function sameVnode(a, b) {
return (
a.key === b.key && (
(
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)
)
)
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
updateChildren
方法中会对新旧 vnode
进行 diff
,然后将比对出的结果用来更新真实的 DOM
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// ...
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
// ...
} else if (isUndef(oldEndVnode)) {
// ...
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// ...
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// ...
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// ...
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// ...
} else {
// ...
}
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
diff 的详细过程可以参考 virtual DOM && Diff (opens new window)