从实际的案例发现到在使用 v-for 时,将 key 值写成了 index,出现的问题。与其使用 index 作为 key ,不如完全不写 key,因为他们的性能是一样。比如一个列表长度为 10,用 index 作为 key ,各 item 的 key 值依次就是 0-9, 然后 reverse() 一下,列表的的 key 还是 0-9。在依次 patch 这 10 个 item 的时候,sameVnode() 全部返回真(因为 tag 和 key 都一样)。再看另外一种情况,如果不写 key , 那这 10 个 item 的 key 全部是 undefined, 在 sameVnode() 也是全部返回真(因为 tag 相同,key 也相同:key 都是 undefined)。所以得出结论,如果使用 index 作为 key,不如干脆不写 key 了,还能省下代码。

一、案例 bug 复现

父组件代码:

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
// 父组件
<Child
v-for="(item,index) in list"
:key="index"
:count="item.count"
:name="item.name"
@delete="handleDelete(index)"
/>

// data
list:[
{
count:1,
name:"1"
},
{
count:2,
name:"2"
},
{
count:3,
name:"3"
},
]
// method
handelDelete(index){
this.list.splice(index,1)
}

子组件代码:

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
// 子组件
<div>
<span>
{{name}}
</span>
count:{{ innerCount }}
<button @click="$emit('delete)"></button>delete</button>
</div>
// props
props:{
count:{
type:Number,
default:0
},
name:{
type:String,
default:''
}
}
// data
data(){
retrun{
// 子组件下面的这行代码定义了自己的状态,无法使用index作为key值
innerCount:this.count
}
}

当删除的时候,看上去是成功了,其实存在问题。加上排序也是存在问题。

将 v-for 里面的 key 换成 item 后,是正常的。

Vue 官网的描述:

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

这个默认的模式是高效的,但是只适用于不依赖子组件装填或临时 DOM 状态的列表渲染输出。

二、总结

写列表渲染时,依赖子组件状态或者临时 DOM 状态的情况,如果存在删除、增加、排序这样的功能,不能将 index 作为 key 值。

这里还包括 diff 算法…(待添加)