文章目录
前言
key的作用(主要):标识节点,以便相同的节点可以被高效复用
要了解key的原理,就得先了解虚拟Dom的渲染过程
一、虚拟Dom渲染
在Vue渲染过程中,Vue会先将初始数据生成虚拟Dom(内存中),生成虚拟Dom后,再通过虚拟Dom生成真实Dom(也就是我们页面上看到的数据)。此时页面存在虚拟Dom和真实Dom两个部分。
当页面的数据发生改变时,也就是修改了Dom节点后,该页面会重新生成一个新的虚拟Dom,此时页面有新旧两个虚拟Dom树,Vue通过对新旧两个虚拟Dom进行比较,从而跟新页面数据,比较的过程使用diff算法,过程如下:
1.旧虚拟Dom中的节点找到与新虚拟Dom中具有与其相同key值的节点进行比较:
- 若新虚拟Dom内容没变,直接复用旧虚拟Dom作为页面的真实Dom
- 若新虚拟Dom内容改变,则生成新的真实Dom,替换掉页面之前的真实Dom
2.旧虚拟Dom的节点未找到与新虚拟Dom具有与其相同key值的节点:
- 创建新的真实Dom,随后渲染到页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>key原理</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>The list of persons</h2>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.id}}--{{p.name}}--{{p.age}}
</li>
</ul>
<button @click="add">Add</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data: {
persons:[
{id:1, name:'san Zhang', age:18},
{id:2, name:'si Li', age:19},
{id:3, name:'wu Wang', age:20}
]
},
methods: {
add() {
const p = {id:4, name:'daPao Zhang', age:21};
this.persons.push(p);
}
},
});
</script>
</body>
</html>
解析:为li节点绑定key,key值设置为人物信息中的唯一标识属性id,通过v-for遍历数组对象,将列表渲染到页面中,此外我们定义一个按钮用于操作节点,为列表添加数据。点击按钮,内存中生成新虚拟Dom如下:
新虚拟Dom:
<li key = 1>san Zhang<li>
<li key = 2>si Li<li>
<li key = 3>wu Wang<li>
<li key = 4>daPao Zhang<li>
旧虚拟Dom:
<li key = 1>san Zhang<li>
<li key = 2>si Li<li>
<li key = 3>wu Wang<li>
新旧虚拟Dom进行比较,通过以上的比较过程,旧虚拟Dom的key值为1、2、3的li节点依次找到新虚拟Dom的key值为1、2、3的li节点进行比较,比较结果相等,因此在比较过程中,旧虚拟Dom的key值为1、2、3的节点得到了复用,不需要再次渲染,而新创建的key值为4的节点渲染到页面中的真实Dom中,从而大大提高了性能。
QQ录屏20220424173951
二、key的使用
1.经典key错误案例:index作为key
用index作为key值可能会引发的问题:
1.若对数据进行逆序添加、删除等破坏节点顺序的操作:会产生没必要的Dom更新,效率低
举个栗子:
<div id="root">
<!-- 遍历数组 -->
<h2>The list of persons</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.id}}--{{p.name}}--{{p.age}}
</li>
</ul>
<button @click="add">Add</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data: {
persons:[
{id:1, name:'san Zhang', age:18},
{id:2, name:'si Li', age:19},
{id:3, name:'wu Wang', age:20}
]
},
methods: {
add() {
const p = {id:4, name:'daPao Zhang', age:21};
this.persons.unshift(p);
}
},
});
</script>
此例我们通过unshift()添加新数据到列表的最前边,同时我们将key值设置为数组的索引值index,生成的新旧虚拟Dom如下:
新虚拟Dom:
<li key = 0>daPao Zhang<li>
<li key = 1>san Zhang<li>
<li key = 2>si Li<li>
<li key = 3>wu Wang<li>
旧虚拟Dom:
<li key = 0>san Zhang<li>
<li key = 1>si Li<li>
<li key = 2>wu Wang<li>
解析:我们仅仅只是通过添加了一行新的数据,并未对原先三个节点进行修改,因而旧虚拟Dom中本该复用的三个节点在新生成的虚拟Dom中,他们的key值各自向后移动了一位,根据新旧虚拟Dom具有相同key值节点进行比较的规则,旧虚拟Dom的三个节点各自进行没有必要的比较,比较后,旧的三条数据进行了重新渲染,从而产生了没必要的Dom更新。
2.绑定key的Dom结构中包含输入类Dom:会产生错误的Dom更新
<div id="root">
<!-- 遍历数组 -->
<h2>The list of persons</h2>
<ul>
<li v-for="(p,index) in person" :key="index">
{{p.id}}--{{p.name}}--{{p.age}}
<input type="text">
</li>
</ul>
<button @click="add">Add</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data: {
person:[
{id:01, name:'san Zhang', age:18},
{id:02, name:'si Li', age:19},
{id:03, name:'wu Wang', age:20}
]
},
methods: {
add() {
const p = {id:04, name:'daPao Zhang', age:21};
this.person.unshift(p);
}
},
});
</script>
错误案例2
此例我们在li节点中添加输入框节点input,并在输入框中输入信息,然后添加人物信息,产生如上错误的Dom更新,这在实际开发中是严重的错误。生成的新旧虚拟Dom如下:
新虚拟Dom:
<li key = 0>daPao Zhang<input type="text"><li>
<li key = 1>san Zhang<input type="text"><li>
<li key = 2>si Li<input type="text"><li>
<li key = 3>wu Wang<input type="text"><li>
旧虚拟Dom:
<li key = 0>san Zhang<input type="text"><li>
<li key = 1>si Li<input type="text"><li>
<li key = 2>wu Wang<input type="text"><li>
解析:同理,新旧Dom中具有相同key值的两两li节点进行比较,先对li内部的文本节点进行比较从而对更新了对应的真实Dom,而对li节点内部input节点比较时,只对比了该节点结构,而不会对input中的value值进行比较,发现都新旧虚拟Dom中的input节点都相同,因此复用旧虚拟Dom中的input节点,导致产生了错误的Dom更新。
2.如何使用key
在Vue开发中使用key标识节点时对页面中的数据进行更新时,最好的方式是采用每条数据的唯一标识属性,如id、手机号、身份证号等唯一值。如果不存在对数据进行逆序添加、删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key值是没有问题的。