key值得作用
- 作用1:主要作用是更高效的更新虚拟dom,减少dom操作,提高渲染性能;
- 作用2:管理可复用的元素
更高效的更新虚拟DOM
-
在使用虚拟dom更新真实dom的过程之中,会通过sameVnode方法判断新旧vnode节点是否相同,若是相同会对比其子节点与文本内容,找出最小差异进行更新;
-
function sameVnode(a, b) { return ( a.key === b.key && // key值是否相同 a.asyncFactory === b.asyncFactory && // 异步工厂方法 ((a.tag === b.tag && // 标签名 a.isComment === b.isComment && // 是否为注释节点 isDef(a.data) === isDef(b.data) && // 是否有属性 sameInputType(a, b)) || // 是否为input节点&type属性值是否相同 (isTrue(a.isAsyncPlaceholder) && // 是否为异步占位符节点 isUndef(b.asyncFactory.error) )) ) }
-
通过源码可以看出 若是元素不设置key值 a.key===undefined b.key= ==undefined 造成a.key= ==b.key会很容易被认定为同一个节点;
-
案例1
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
</ul>
以上是目前的真实DOM,现在要在 B元素 后面插入一个 F元素
<li>F</li>
存在key值
<template>
<div>
<ul>
<li v-for='item in data' :key='item'>{{item}}</li>
</ul>
<el-button @click='test'>按钮</el-button>
</div>
</template>
<script>
export default {
data(){
return{
data:['A','B','C','D','E']
}
},
methods:{
test(){
this.data=['A','B','F','C','D','E']
},
}
}
</script>
(1)前提:
- 比较过程中 会生成四个指针 oldVnode的头部、oldVnode的尾部(后面称为旧前与旧后);vnode的头部、vnode的尾部(后面称为新前和新后)
- 遍历两个列表---->遍历过程中比较顺序为 旧前-新前、旧后-新后、旧前-新后、旧后-新前;
(2)比较过程:
- [1]oldVA 与VA为同一元素;
- patchA;
- 指针后移;
- [2]oldVB与VB为同一元素;
- patchB;
- 指针后移;
- [3]oldVC与VF的key值不相同,不是同一节点;
- 指针位置不变
- 进行旧后-新后比较;
- [4]oldVE 与VE为同一元素;
- patchE;
- 指针前移;
- [4]oldVD 与VD为同一元素;
- patchD;
- 指针前移;
- [5]oldVC 与VC为同一元素
- patchC
- 指针前移
- [6]旧前>旧后
- 说明vnode元素比较多,根据vnode创建真实dom F 并插入
- 结束
(3)总结
- 进行了一次创建,0次更新;
不存在key值
<template>
<div>
<ul>
<li v-for='item in data' >{{item}}</li>
</ul>
<el-button @click='test'>按钮</el-button>
</div>
</template>
<script>
export default {
data(){
return{
data:['A','B','C','D','E']
}
},
methods:{
test(){
this.data=['A','B','F','C','D','E']
},
}
}
</script>
(1)前提:
- 比较过程中 会生成四个指针 oldVnode的头部、oldVnode的尾部(后面称为旧前与旧后);vnode的头部、vnode的尾部(后面称为新前和新后)
- 遍历两个列表---->遍历过程中比较顺序为 旧前-新前、旧后-新后、旧前-新后、旧后-新前;
(2)比较过程:
- [1]oldVA 与VA为同一元素;
- patchA;
- 指针后移;
- [2]oldVB与VB为同一元素;
- patchB;
- 指针后移;
- [3]oldVC与VF为同一元素;
- patchC-需更新;
- 指针后移;
- [4]oldVD与VC为同一元素;
- patchD-需更新;
- 指针后移;
- [5]oldVE与VD为同一元素;
- patchE-需更新;
- 指针后移;
- [6]旧前>旧后
- 说明vnode元素比较多,根据vnode创建真实dom E 并插入
- 结束
(3)总结
- 进行1次创建,3次更新
- 效率较有key值较低;
案例2
<!-- 更新前-->
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
<li>f</li>
</ul>
<!--更新后-->
<ul>
<li>b</li>
<li>f</li>
<li>g</li>
</ul>
存在key值
<!-- 更新前-->
<ul>
<li key='a'>a</li>
<li key='b'>b</li>
<li key='c'>c</li>
<li key='d'>d</li>
<li key='e'>e</li>
<li key='f'>f</li>
</ul>
<!--更新后-->
<ul>
<li key='b'>b</li>
<li key='f'>f</li>
<li key='g'>g</li>
</ul>
(1)前提:
- 比较过程中 会生成四个指针 oldVnode的头部、oldVnode的尾部(后面称为旧前与旧后);vnode的头部、vnode的尾部(后面称为新前和新后)
- 遍历两个列表---->遍历过程中比较顺序为 旧前-新前、旧后-新后、旧前-新后、旧后-新前,若是前4种都没有命中;
- [1]存在key值
- [2]不存在key值
(2)比较过程
- [1]旧前-新前、旧后-新后、旧前-新后、旧后-新前都未命中;
- [2]通过key值找出 新前元素b 在oldVnode中的索引
- 发现oldVnode元素b—>操作真实dom,将b元素提至旧前,当前位置置空;
- 新前指针后移
- [3]通过key值找出 新前元素f 在oldVnode中的索引
- 发现oldVnode元素f—>操作真实dom,将f元素提至旧前,当前位置置空;
- 新前指针后移;
- [4]通过key值找出 新前元素g 在oldVnode中的索引
- 没有找到对应索引 —> 创建新元素并将dom插入;
- 新前指针后移;
- [5]新前>新后
- 删除oldVnode多余节点
- 结束
管理可复用的元素
因为Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求;
案例
<body>
<div id="app">
<span v-if="byUsername">
<label for="username">用户账号</label>
<input type="text" id="username" placeholder="用户账号">
</span>
<span v-else>
<label for="email">用户邮箱</label>
<input type="text" id="email" placeholder="用户邮箱">
</span>
<button @click="byUsername =! byUsername">切换登录方式</button>
</div>
</body>
<script>
const app = new Vue({
el: '#app',
data: {
byUsername: true
}
})
</script>
- 问题复现
- [1]在使用账号登录页面 输入用户账号
- [2]点击 切换登录方式 按钮 想要使用用户邮箱登录
- [3]虽然页面变为邮箱登录页面 但是输入框中内容并未清空
- 原因:由于标签相同,因此vue在渲染过程种并没有创建标签,而是直接复用;
- 解决:添加key值 表示元素不是同一元素!