16_Vue列表渲染中key的工作原理和虚拟DOM对比算法
key的作用
- 粗略的讲,key的作用就是给 节点 设置一个 唯一的标识
- 就像我们人类社会中,每个人的身份证号一样
- 在大部分对key要求不是很严格的场景下,使用index作为key是没问题的
- 但是我们本章要探讨的是,其他情况,可能会出现问题的情况
来看个例子
案例
-
这里呢,有个ul标签
-
在内部,li标签通过v-for渲染 data当中的persons数组
-
-
<script> var vm = new Vue({ el: '.app', data: { name: 'wavesbright', persons:[ {id:1,name:'张三',age:18}, {id:2,name:"李四",age:19}, {id:3,name:"王五",age:20}, ], }, }); </script>
-
-
得到了三个节点(3个li)
<div class="app">
<h1>遍历数组</h1>
<ul>
<li v-for="(item,index) in persons" :key="index">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
我现在提一个需求
需求
- 我添加一个按钮,这个按钮会给我添加一个 老刘 这个对象(老六)
- 这个老刘呢,不能添加在 persons数组的最后面,要在最前面
- 不然看不出问题
click方法只调用一次哈,多了不好分析,这里使用的是事件修饰符
<div class="app">
<h1>遍历数组</h1>
<!-- 添加一个人,叫老刘 -->
<button @click.once="addPerson">添加</button>
<ul>
<li v-for="(item,index) in persons" :key="index">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
<script>
var vm = new Vue({
el: '.app',
data: {
name: 'wavesbright',
persons:[
{id:1,name:'张三',age:18},
{id:2,name:"李四",age:19},
{id:3,name:"王五",age:20},
],
},
methods: {
// 添加成员 == 老刘
addPerson(){
// 这是老刘
var laoliu = {id:4,name:'老刘',age:'xx'}
// 在数组顶部添加成员
this.persons.unshift(laoliu)
}
},
});
</script>
测试
感觉没什么问题呀,为什么会讲这个呢
警告,错误没有,页面显示正常,也没有报错
有问题吗,有,这里面有个很严重的问题
增加需求
- 我现在再来一个需求
- 给每个 li标签当中,添加一个input框
再来测试下
没问题啊,咋地?来,我给你演示下
问题出现
- 我在添加老刘之前,我先给张三李四王五的input框框中,输入一些文字
- 然后添加老刘,我们来看看这次是什么样子的
测试
- 我们希望看到的是什么? == 老刘出现的时候,是一个空白的input框
- 但现在问题是什么?下方的三个兄弟的信息 分别错位了 为什么会这样
- 那看看 现在我将 :key换成 item.id会怎么样
再测试
哦~那我们现在可以总结一个情况
- 使用index会出现这个问题
- 但是使用自带的id就没这个问题,这是我们现在所遇到的情况
key的工作原理和对比算法
这里使用流程图讲解
分析index作为key
初始化流程
1、一切的一切都是你写了这段代码
2、vue是不是会拿着你的数据生成 虚拟DOM?(并不是一开始就把数据给你变为页面DOM的,是有流程的)
3、真实DOM上是没有这个key的,虚拟DOM上必须要有(没有,vue不能高效工作)
4、上图这个是已经生成真实DOM了,我们现在还处于虚拟DOM生成的过程中(假设现在页面还没有生成这三个li标签)。现在,在内存当中是不是有这三个虚拟DOM了(3个li标签)
5、将虚拟DOM转换为真实DOM
6、请问,用户是在哪里操作的数据?虚拟DOM还是真实DOM,用户输入的数据,残留在谁身上了?虚拟还是真实DOM?(用户操作的全是真实DOM)
这个时候初始化流程就结束了
数据更新,老刘出现
1、新的数据出现了,老刘出现了,老刘是排在所有人的前面
2、随后,会根据新的数据,生成新虚拟DOM(因为数据发生改变了)
这个时候,老刘的key就是0了,因为你使用index作为索引,老刘排在最前面(因为我们插在最前面)
重点来了:,在目前的整个流程当中,生成了两份虚拟DOM,vue不会根据上面这个修改后的虚拟DOM进行真实DOM的创建,而是会将两个虚拟DOM进行一个对比算法,这就是该算法的由来
虚拟DOM的对比算法
对比的时候,依赖着这个key,怎么对比的呢?请让我用文字来为你形容
- 首先,它来到这个新的虚拟DOM当中,按照顺序,先取出第一个(老刘)
- 然后它就问,你的key是多少,老刘回答 0
- 接下来,它来到旧的虚拟DOM中,寻找和 老刘拥有相同key值的人 谁呢? 张三吖
- 找打了,咋的呢?
- 它会对比两个节点的内容
- 怎么对比的呢?
- 拿出 右侧的 老刘-30 与 左侧的 张三-18 ,一对比,诶,两边不相等
- 不一样了,怎么着?我们可以得出一点,key值为0的两个虚拟DOM内容不相同
- 接下来,因为二者内部都存在 input,开始对比input的内容
- 这里先暂停下,我来问个问题?请问,对比二者的input的时候,它们input的内容是否相等?
- 答案是相等的,因为这俩个都是虚拟DOM
- 用户输入数据的时候,数据是残留在真实DOM中的
- 人家是在内存当中对比的虚拟DOM,
- 最终input这里对比的结果就是相等的
- 刚刚对比不一样(老刘-30与张三-18)的怎么办,一样的(input)怎么办呢?
- 一样的结果就是 复用
- 什么是复用呢?
- 你看,我老刘这里有个input,你张三这里也有个input
- 我老刘的key是0,你张三的key也是0
- 那么作为唯一标识,咱俩是来自同一个体系的
- 要不你看这样吧,你这个张三的虚拟DOM一定转换过真实DOM(都用对比算法了肯定转换过)
- 你看昂,张三的input转换过真实DOM,那么我老刘的input与张三的input是一样的(虚拟DOM中)
- 那我老刘就没必要把 li 当中 的 input转换为 真实DOM了
- 我直接拿 张三的 input(真实) 复用
- 那我老刘就没必要把 li 当中 的 input转换为 真实DOM了
- 也就出现了下面的情况
- 虚拟DOM进行了对比
- 对比的结果 决定了 还是使用之前 张三的 真实input (不需要将老刘的虚拟input转换为真实input)
- 然鹅,张三的真是input当中,还残留着用户的输入
- 那么,搬用过来的时候,把残留输入一起带过来了
- 错在哪里
- 使用index作为key,导致了 复用 的发生,顺序乱了
- 由于这个细节错误的出现,导致了错位的生成
- 以此类推,到王五的时候,key = 3在旧的虚拟DOM当中不存在,那么只有自己生成了
我们出现这个错误的原因是执行了这个奇葩的需求,但是通过这个需求,我们能对key进行更加深入的理解
为什么说效率低
- 我们来看图,图里解释的很清楚了
- 二者的input是相等的,在虚拟DOM当中
- 但是二者的 插值语法这个位置
- 是不相等的,不相等,那么就没有办法采用 复用行为
- 没有办法采用复用行为,那么 插值语法这里的真实DOM就需要自己生成
- 也就是这一块,是新的虚拟DOM转换为真实DOM自己生成的
分析item.id作为key
通过上面的分析,这里思路就很清晰了
- 还是一样的,因为这些初始化数据,我们在内存当中生成了虚拟DOM
- 虚拟DOM转换为真实DOM
- 用户在真实DOM进行操作,残留了输入
- 接下来我们添加了一个成员,老刘
- 那么虚拟DOM被更新了
- 更新了,那么就会和旧的虚拟DOM进行对比
- 老刘的key是004,旧的DOM当中是没有key为004的元素的,那么老刘就需要自己生成
- 张三的key是001,旧的虚拟DOM当中有吗?有的,那么input一样吗,一样的,那就复用呗
- 同理李四和王五也是
- 老刘的key是004,旧的DOM当中是没有key为004的元素的,那么老刘就需要自己生成
- 所以使用id作为key是不会出现刚刚那个问题的
- 效率高吗,高,因为服用了,错位了吗,没有
不写key
- 如果你不写key,那么vue在遍历的时候会默认将索引值index作为key
- 既然index作为key了,这个DOM身上是有key的,那么自然不会报错
- 既然index作为key了,遇见刚刚那个问题自然会出现问题
面试题
react、vue中的key有什么作用?
分为如下几步回答