一、想要解决的问题
假设你有三个子组件,每个子组件里面有一个「有状态的」子组件。
A => a
B => b ×
C => c
现在我们把第二个组件删掉
A => a
B => c
为什么会这样?
二、计算机的遍历
[1,2,3]和 [1,3]
我们第一感觉是删除中间的
但是计算机会对数组遍历
对比1和1,发现1没变
然后对比2和3,发现2变成了3
最后对比undefined 和3,发现3被删除了
所以计算机的结论是:
「2变成了 3」以及「3被删除了」
三、我们的问题
现在回过头来看我们的问题
A => a
B => b ×
C => c
现在我们把第二个组件删掉
A => a
C => b
计算机会遍历
对比A和A,发现A没变
然后对比B和C,发现B变成了C
最后对比undefined 和C,发现3被删除了
子组件删除了,但是孙子组件没有被删除
四、虚拟dom
上面那个对比的过程就是,Diff算法,vue和react的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设:
1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
2. 同一层级的一组节点,他们可以通过唯一的id进行区分。
当页面的数据发生变化时,Diff算法只会比较同一层级的节点
当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况:(上面的比较过程)
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的(变化后和变化前的比较):
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
所以一句话,key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
对于每一层节点是如何处理的呢?
原来的节点A(也可以为一组节点)去和转换后的节点C(也可以为一组节点)比较发现并不相同,则会创建节点C并删除节点A,节点B(也可以为一组节点)比较发现相同不变化,节点C和A比较发现不同,创建节点A,删除节点C.如此,该层的节点就由A B C转化成了 C B A 。
如此这番折腾,cpu估计会累的够呛,既然这些节点本身没有发生变化,只是位置变了,有没有可能只是去调整节点的位置而不用去做如此之多的创建,删除操作呢?必然是有的。
我们可以对同一层级的同组节点添加一个唯一的key进行区分,此处的key就好比数据库里面主键的概念,通过它可以唯一的确定一组节点。
这样vue就可以识别出每一组节点,经过比较key发现,A,B,C都是相同的。只是位置发生了变化,于是他就只是去做移动操作调整位置,而不是去做创建和删除的操作了,效率大大提高。
五、问题解决啦
加了key之后,计算机不会产生只是2=>3这种想法,而是整个组件都变了
上面比较的过程就是Diff算法
六、index
很多情况下,我们使用数组的index来作为key,如果你用index作为key,那么在删除第二项的时候, index 就会从12 3变成1 2
这样会有bug,当你想用index,记得有这个bug就行