今天思考一个问题,在子组件中,key值的作用是什么?
如果一个组件,改边key的值,,发生什么?
实践出真理,测试一下:
首先,创建一个子组件:
//components/keyCom.vue
{{ptext}}
一个非常简单的组件,在各个生命周期上,绑定事件:
export default {
data(){
return {
ptext:"测试文本"
}
},
beforeCreate(){
console.log('enter beforeCreate')
},
created(){
console.log('enter created')
},
beforeMount(){
console.log('enter beforeMount')
},
mounted(){
console.log('enter mounted')
},
beforeDestroy(){
console.log('enter beforeDestroy')
},
destroyed(){
console.log('enter destroyed')
}
}
接下来,通过父组件修改子组件的key值:
import KeyCom from '@/components/keyCom'
export default {
data(){
return {
key:1
}
},
components:{
KeyCom
},
methods:{
changeKey:function(){
this.key=2;
}
}
}
看看运行起来是什么情况:
切换key值之前
点击按钮之后,发现:
修改了key值之后
组件经历了一个全新的生命周期,这是为何?为什么同样一个组件,仅仅改变了它上面的key值,就会重新挂载一个新组件?
之前了解到,key值的最大作用,是在渲染列表的时候,diff算法使用到,那么我们就来看看diff的过程是如何?
分析vue的源码,可以知道,diff算法是从patch函数开始:
patch:function(oldVnode,vnode){
if(sameVnode(oldVnode,vnode)){
patchVnode(oldVnode,vnode)
} else {
const oEl = oldVnode.el;
let parentEle = api.parentNode(oEl);
createEle(vnode);
if(parent!==null){
api.insertBefore(parentEle,vnode.el,api.nextSibling(oEl));
api.removeChild(parentEle,oldVnode.el);
oldVnode = null
}
}
return vnode;
},
通过patch函数,可以看到,首先需要对比两个节点是否是相同节点,(相同的组件,难道不是相同节点吗?)
进入sameVnode函数看看:
sameVnode(a,b){
return (
a.key === b.key && // key值
a.tag === b.tag && // 标签名
a.isComment === b.isComment && //是否为注释节点
//是否都定义了data,data包含一些具体信息,例如onclick
isDef(a.data) === isDef(b,data) &&
sameInputType(a,b) //当标签是input,type必须相同
)
}
恍然大悟,原来在diff的时候,不仅是对比元素的标签名,还会去对比元素的key值,key值一旦改变,就算子节点的内容一模一样,也是会进入到patch函数的else中,那么这个时候,执行的操作就是新建新组件=>删除旧组件=>添加新组件。
因此,可以看到生命周期是新组件的生命周期先执行,再进行旧组件的销毁,接着挂载新组件。
emmmm...
那么再思考深一层的问题,如果是列表渲染的时候,key值设为id,和index会有什么区别呢??
同样的做一个测验:创建一个子组件,子组件里包含一个孙子组件
{{text}}
delete
export default {
name: "Child",
props: ["text"],
data() {
return {
x: "在这输入"
};
},
methods: {
onDelete() {
this.$emit("delete");
}
}
};
接着,在原先的组件里:
创建2个Child组件,它们的区别就是一个使用index作为key,一个使用id作为key:
data(){
return {
ptext:"测试文本",
array: ['111','222','333'],
array2: [{id:1,value:'文本1'},{id:2,value:'文本2'},{id:3,value:'文本3'}]
}
}
运行之后就可以看到它们的区别:
image.png
先看上面的三行,这个是使用index作为key值的组件,当修改其中222这行的input值,然后点击删除:
改变第二行的input值
删除第二项之后
删除之后发现,这与我们的预知不符呀,因为 data 里的数组从 [1,2,3] 变成了 [1,3]。
这个可以看到vue中数组遍历的规则:首先对比1和1,发现1没变,然后对比2和3,发现2变成了3,接着对比3和undefined,把3删掉。
所以步骤是:2变成3=>删除3。
那么在删除的时候,因为input的值是孙子组件,里面的值不受2变成3的影响,所以就地复用。
再看下面这个列表,使用id作为key值。当我们修改了第二项的input值,然后删除第二项的时候,会把第二项完全删掉,符合我们的预期:
修改第二项的input值
删除第二项之后
原本的数组是:
array2: [
{id:1,value:'文本1'},
{id:2,value:'文本2'},
{id:3,value:'文本3'}
]
点击删除之后数组是:
array2: [
{id:1,value:'文本1'},
{id:3,value:'文本3'}
]
先对比id从[1,2,3]变成了[1,3],即第二项被删除了。
因此:key值为何不能用index作为值?
如果你用index作为key值的时候,在删除第二项时,index就从1,2,3变成1,2;而不是1,3。
结论
VUE是通过比对组件自身新旧vdom进行更新的。key的作用是辅助判断新旧vdom节点在逻辑上是不是同一个对象。
因此可以确定,渲染列表时,key值需要一个唯一确定的id来赋值。