一、引言
日常开发中,我们经常会用到v-for指令,处理列表渲染等涉及动态元素增删改场景,为我们带来了遍历数组数据的便捷与高效。在使用v-for时,一个不可或缺的伙伴便是key属性,它的的作用和原理是什么呢?
二、key的作用与原理
key 作用 :用来作为节点的唯一标识
当为一个列表绑定一个key属性时,该属性会存在于虚拟的DOM中,key是虚拟DOM对象的标识,当数据发生变化时,会生成新的虚拟DOM。随后Vue会使用Diff算法进行新虚拟DOM与旧虚拟DOM的差异比较。进行比较的过程可以分成两种情况:
- 当旧虚拟DOM中找到了与新虚拟DOM相同的key时,若虚拟DOM中内容没变, 直接使用之前的真实DOM;若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM;
- 当旧虚拟DOM中未找到与新虚拟DOM相同的key时,创建新的真实DOM,随后渲染到到页面。
三、情景举例
举例一:在v-for中不使用key属性(vue会把默认索引作为key的唯一标识)
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>key的作用以及原理</title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="(list,index) in userinfo">{{list.username +"-"+ list.age}}</li>
</ul>
<button @click="add">点击添加信息</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data: {
msg:{username:"赵六",age:20},
userinfo: [{
username: "张三",
age: 17
}, {
username: "李四",
age: 18
}, {
username: "王五",
age: 19
}]
},
methods:{
add() //用来点击向userinfo头部添加一条数据
{
this.userinfo.unshift(this.msg)
}
}
})
</script>
</body>
</html>
运行结果如下图:
根据运行结果,大家可以看到不写key属性也能正常地显示我们想要的效果,所以为何要多加一个key属性呢?
事实上,当你没有添加key属性时,系统会默认将绑定的key属性的值指向index(列表的索引),例如:{username:'张三',age:17}的index值就为0,{username:'李四',age:18}的index值就为1,依次类推,为每一个li标签加上标识。
举例二:在v-for中使用key属性
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>key的作用与原理</title>
<script src="vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<button @click.once="add">添加新用户</button>
<ul>
<!-- 绑定了key属性,并将其值指向于index -->
<li v-for="(p,index) of persons" :key="index">
{{p.username}}-{{p.age}}
<!-- 增加了input输入框 -->
爱好: <input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
persons:[
{id:'001',username:'张三',age:17},
{id:'002',username:'李四',age:18},
{id:'003',username:'王五',age:19}
]
},
methods: {
add(){
const p = {id:'004',username:'赵六',age:20}
this.persons.unshift(p)
}
},
})
</script>
</html>
通过以上的运行结果我们可以很明显地看出问题,当爱好输入框没有内容时,添加之后并没有什么问题,但是当我们在添加新用户之前先输入现有用户的爱好时,我们会发现添加之后出现了输入框错乱的问题,为何会出现这种问题呢?
当我们把index作为key时,初始数据之后会根据数据生成虚拟的DOM,对应的li标签中的key值分别是0,1,2。当数据生成初始虚拟DOM之后,会创建新的真实DOM,随后渲染到到页面。而当我们再添加一条数据时,会根据我们新的数据来生成新的虚拟DOM。新的虚拟DOM会与初始的也就是旧的虚拟DOM来进行对比,先找到对应相同的key值的li,若虚拟DOM中内容没变, 直接使用之前的真实DOM,若虚拟DOM中内容变了, 则生成新的真实DOM。
对比发现key="0"中的内容有变化,从"张三-17"变成了"赵六-20",随后替换掉页面中之前的真实DOM。而爱好:<input type="text">对比的结果是相同的,因此保存原本的内容。key="1"以及key="2"以此类推。而key="3"并没有在旧虚拟DOM中找到,因此创建新的真实DOM,随后渲染到到页面。
举例三:在v-for中使用数据唯一标识作为key值
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>key的作用与原理</title>
<script src="vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<button @click.once="add">添加新用户</button>
<ul>
<!-- 绑定了key属性,并将其值指向于index -->
<li v-for="(p,index) of persons" :key="p.id">
{{p.username}}-{{p.age}}
<!-- 增加了input输入框 -->
爱好: <input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
persons:[
{id:'001',username:'张三',age:17},
{id:'002',username:'李四',age:18},
{id:'003',username:'王五',age:19}
]
},
methods: {
add(){
const p = {id:'004',username:'赵六',age:20}
this.persons.unshift(p)
}
},
})
</script>
</html>
我们可以明显地看出,若我们的key属性绑定的值为数据中的唯一标识时,我们就能够得到我们想要的结果。对应的原理图如下:
每一个li标签中绑定的值为id,因此当添加一个新成员时,对应的key值为004,并没有在旧的虚拟DOM中找到,因此添加到真实的DOM中并渲染在页面上。其他的根据key值比较没有变化,保留原本的内容。
四、结语
若使用index作为key需要考虑如下两个问题:
- 若对数据进行逆序添加、逆序删除等破坏顺序的操作时, 会产生没有必要的真实DOM更新 ,虽然界面效果没问题,但效率低;
- 如果结构中还包含输入类的DOM,会产生错误DOM更新 ,界面会产生问题。
我们在开发中,首先最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号等唯一值。 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,用index作为key是没有问题的。