今天思考一個(gè)問(wèn)題搓幌,在子組件中杆故,key值的作用是什么?
如果一個(gè)組件溉愁,<A key="1" />
改邊key的值处铛,<A key="2" />
,發(fā)生什么?
實(shí)踐出真理撤蟆,測(cè)試一下:
首先奕塑,創(chuàng)建一個(gè)子組件:
//components/keyCom.vue
<template>
<div>
<p>{{ptext}}</p>
</div>
</template>
一個(gè)非常簡(jiǎn)單的組件,在各個(gè)生命周期上家肯,綁定事件:
export default {
data(){
return {
ptext:"測(cè)試文本"
}
},
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')
}
}
接下來(lái)龄砰,通過(guò)父組件修改子組件的key值:
<template>
<div>
<Key-Com :key="key" />
<a class='change-btn' @click='changeKey()'>切換key</a>
</div>
</template>
<script>
import KeyCom from '@/components/keyCom'
export default {
data(){
return {
key:1
}
},
components:{
KeyCom
},
methods:{
changeKey:function(){
this.key=2;
}
}
}
</script>
看看運(yùn)行起來(lái)是什么情況:
點(diǎn)擊按鈕之后,發(fā)現(xiàn):
組件經(jīng)歷了一個(gè)全新的生命周期息楔,這是為何寝贡?為什么同樣一個(gè)組件,僅僅改變了它上面的key值值依,就會(huì)重新掛載一個(gè)新組件?
之前了解到碟案,key值的最大作用愿险,是在渲染列表的時(shí)候,diff算法使用到价说,那么我們就來(lái)看看diff的過(guò)程是如何辆亏?
分析vue的源碼,可以知道鳖目,diff算法是從
patch
函數(shù)開(kāi)始:
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;
},
通過(guò)patch函數(shù)扮叨,可以看到,首先需要對(duì)比兩個(gè)節(jié)點(diǎn)是否是相同節(jié)點(diǎn)领迈,(相同的組件彻磁,難道不是相同節(jié)點(diǎn)嗎?)
進(jìn)入sameVnode函數(shù)看看:
sameVnode(a,b){
return (
a.key === b.key && // key值
a.tag === b.tag && // 標(biāo)簽名
a.isComment === b.isComment && //是否為注釋節(jié)點(diǎn)
//是否都定義了data,data包含一些具體信息狸捅,例如onclick
isDef(a.data) === isDef(b,data) &&
sameInputType(a,b) //當(dāng)標(biāo)簽是input,type必須相同
)
}
恍然大悟衷蜓,原來(lái)在diff的時(shí)候,不僅是對(duì)比元素的標(biāo)簽名尘喝,還會(huì)去對(duì)比元素的key值磁浇,key值一旦改變,就算子節(jié)點(diǎn)的內(nèi)容一模一樣朽褪,也是會(huì)進(jìn)入到patch函數(shù)的else中置吓,那么這個(gè)時(shí)候,執(zhí)行的操作就是新建新組件=>刪除舊組件=>添加新組件缔赠。
因此衍锚,可以看到生命周期是新組件的生命周期先執(zhí)行,再進(jìn)行舊組件的銷毀橡淑,接著掛載新組件构拳。
emmmm...
那么再思考深一層的問(wèn)題,如果是列表渲染的時(shí)候,key值設(shè)為id置森,和index會(huì)有什么區(qū)別呢斗埂??
同樣的做一個(gè)測(cè)驗(yàn):創(chuàng)建一個(gè)子組件凫海,子組件里包含一個(gè)孫子組件
<template>
<div>
{{text}}
<input v-model="x">
<button @click="onDelete">delete</button>
</div>
</template>
<script>
export default {
name: "Child",
props: ["text"],
data() {
return {
x: "在這輸入"
};
},
methods: {
onDelete() {
this.$emit("delete");
}
}
};
</script>
接著呛凶,在原先的<key-com/>組件里:
<template>
<div>
<Child class="child" v-for="(item,i) in array" :key='i' :text="item" @delete="remove(i)" />
<Child class="child" v-for="(item,i) in array2" :key='item.id' :text="item.value" @delete="remove2(i)" />
</div>
</template>
創(chuàng)建2個(gè)Child組件,它們的區(qū)別就是一個(gè)使用index作為key行贪,一個(gè)使用id作為key:
data(){
return {
ptext:"測(cè)試文本",
array: ['111','222','333'],
array2: [{id:1,value:'文本1'},{id:2,value:'文本2'},{id:3,value:'文本3'}]
}
}
運(yùn)行之后就可以看到它們的區(qū)別:
先看上面的三行漾稀,這個(gè)是使用index作為key值的組件,當(dāng)修改其中222這行的input值建瘫,然后點(diǎn)擊刪除:
刪除之后發(fā)現(xiàn)崭捍,這與我們的預(yù)知不符呀,因?yàn)?data 里的數(shù)組從 [1,2,3] 變成了 [1,3]啰脚。
這個(gè)可以看到vue中數(shù)組遍歷的規(guī)則:首先對(duì)比1和1殷蛇,發(fā)現(xiàn)1沒(méi)變,然后對(duì)比2和3橄浓,發(fā)現(xiàn)2變成了3粒梦,接著對(duì)比3和undefined,把3刪掉荸实。
所以步驟是:2變成3=>刪除3匀们。
那么在刪除的時(shí)候,因?yàn)閕nput的值是孫子組件准给,里面的值不受2變成3的影響泄朴,所以就地復(fù)用。
再看下面這個(gè)列表圆存,使用id作為key值叼旋。當(dāng)我們修改了第二項(xiàng)的input值,然后刪除第二項(xiàng)的時(shí)候沦辙,會(huì)把第二項(xiàng)完全刪掉夫植,符合我們的預(yù)期:
原本的數(shù)組是:
array2: [
{id:1,value:'文本1'},
{id:2,value:'文本2'},
{id:3,value:'文本3'}
]
點(diǎn)擊刪除之后數(shù)組是:
array2: [
{id:1,value:'文本1'},
{id:3,value:'文本3'}
]
先對(duì)比id從[1,2,3]變成了[1,3],即第二項(xiàng)被刪除了油讯。
因此:key值為何不能用index作為值详民?
如果你用index作為key值的時(shí)候,在刪除第二項(xiàng)時(shí)陌兑,index就從1沈跨,2,3變成1兔综,2饿凛;而不是1狞玛,3。
結(jié)論
VUE是通過(guò)比對(duì)組件自身新舊vdom進(jìn)行更新的涧窒。key的作用是輔助判斷新舊vdom節(jié)點(diǎn)在邏輯上是不是同一個(gè)對(duì)象心肪。
因此可以確定,渲染列表時(shí)纠吴,key值需要一個(gè)唯一確定的id來(lái)賦值硬鞍。