您好,登錄后才能下訂單哦!
vue組件間怎么實(shí)現(xiàn)事件傳遞,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
由于新工作需要用vue,所以最近接觸最多的也是vue,因?yàn)橹耙恢痹谟胷eact,所以對(duì)于vue上手還是很快的。
我也盡量找一些他們兩個(gè)的異同點(diǎn),除了多了一些輔助用的方法以外,最大的不同應(yīng)該是對(duì)于組件間的通信,不僅有props,還有一種事件監(jiān)聽(tīng),也是可以通過(guò)組件間傳遞的。
但是,在vue2.+中,vue引入了diff算法和虛擬dom來(lái)提升效率。我們知道這些事為了處理頻繁更新dom元素所提出的一種優(yōu)化方案,可頻繁變動(dòng)更新以及事件監(jiān)聽(tīng)的初始化之間是否會(huì)有矛盾,當(dāng)組件需要變動(dòng)時(shí),有沒(méi)有對(duì)注冊(cè)過(guò)的事件進(jìn)行解綁? 我們來(lái)寫(xiě)一些簡(jiǎn)單的代碼印證一下。
我們寫(xiě)兩個(gè)div做的按鈕,一個(gè)是寫(xiě)的html代碼,一個(gè)是通過(guò)組件的形式插入,兩個(gè)按鈕完全一樣,但我們加一個(gè)disabled的屬性在外層,并通過(guò)if-else來(lái)判斷disabled從而顯示不同的按鈕(當(dāng)然正常場(chǎng)景下我們不會(huì)這么去寫(xiě)代碼,這里只是通過(guò)這種方式模擬一種特殊場(chǎng)景,我們自行考慮在我們的業(yè)務(wù)中是否存在這種場(chǎng)景)。
<template> <div class="test"> <div class="btn" v-if="disabled" @click="handleClick">可點(diǎn)擊</div> <div class="btn" v-else >不可點(diǎn)擊</div> <Button v-if="disabled" @clickTest="handleClick">可點(diǎn)擊</Button> <Button v-else>不可點(diǎn)擊</Button> </div> </template> <script> import Button from './Button' export default { data () { return { disabled: true } }, methods: { handleClick() { alert('可點(diǎn)擊') } }, components: { Button, }, mounted() { setTimeout(() => { this.disabled = false }, 1000) } } </script> <style> .btn{ margin: 100px auto; width: 200px; line-height: 50px; border: 1px solid #42b983; border-radius: 5px; color: #42b983; } </style>
我們加一點(diǎn)樣式,讓他盡量好看一點(diǎn),看著很簡(jiǎn)單,兩個(gè)按鈕,可點(diǎn)擊時(shí)為他綁定一個(gè)點(diǎn)擊事件,不可點(diǎn)擊時(shí)不為他綁定。不同點(diǎn)是一個(gè)是直接寫(xiě)的html代碼,一個(gè)是組件。組件的代碼如下:
<template> <div class="btn" @click="handleClick"><slot></slot></div> </template> <script> export default { methods: { handleClick() { this.$emit('clickTest') } } } </script>
然后在mounted周期里加一個(gè)1秒的settimeout將disabled變?yōu)閒alse,然后我們測(cè)試一下
當(dāng)disabled還是true得時(shí)候,兩個(gè)按鈕點(diǎn)擊都會(huì)彈出可點(diǎn)擊的alert。但當(dāng)disebled變?yōu)閒alse的時(shí)候,上面用html寫(xiě)的不會(huì)再?gòu)椏?,可下面用組件寫(xiě)的就還是會(huì)彈窗。
這種問(wèn)題出現(xiàn)時(shí)是非常不好定位的,因?yàn)榇a上很顯然不會(huì)去調(diào)取這個(gè)clicktest事件,而在頁(yè)面上,我們也能確定按鈕已經(jīng)變?yōu)椴豢牲c(diǎn)擊的那一個(gè)了。那為什么這個(gè)事件還是會(huì)被調(diào)取呢?
這先要從diff算法說(shuō)起,傳統(tǒng)的diff tree算法的算法復(fù)雜度是O(n^3),而react在引入diff算法時(shí),拋除了跨級(jí)移動(dòng)的情況,即只比對(duì)同一級(jí)的節(jié)點(diǎn)異同,讓算法復(fù)雜度降低到了O(n),讓我們可以肆無(wú)忌憚(當(dāng)然也要適可而止)的頻繁刷新整個(gè)頁(yè)面。
(呵呵,沒(méi)圖)
diff有一條策略是擁有相同類的兩個(gè)組件將會(huì)生成相似的樹(shù)形結(jié)構(gòu),擁有不同類的兩個(gè)組件將會(huì)生成不同的樹(shù)形結(jié)構(gòu)。所以它的比對(duì)順序就是
1)tree diff
2)component diff
3)element diff
回到我們的代碼上,我們?cè)谶M(jìn)行component diff時(shí),認(rèn)為他們是相同的組件,然后進(jìn)行element diff,即進(jìn)行新增 刪除和移動(dòng)所以問(wèn)題就是發(fā)生在了這里,在實(shí)例化組件的時(shí)候我們初始化了事件監(jiān)聽(tīng),但在替換相同組件里的dom時(shí),vue并沒(méi)有對(duì)已添加到組件上的事件監(jiān)聽(tīng)做刪除。
我們看一下vue的代碼,
Vue.prototype.$emit = function (event: string): Component { const vm: Component = this if (process.env.NODE_ENV !== 'production') { const lowerCaseEvent = event.toLowerCase() if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${hyphenate(event)}" instead of "${event}".` ) } } let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) for (let i = 0, l = cbs.length; i < l; i++) { try { cbs[i].apply(vm, args) } catch (e) { handleError(e, vm, `event handler for "${event}"`) } } } return vm }
vue是通過(guò)vdom里的_events屬性下確定是否有綁定事件的。我們看一下不可點(diǎn)擊的按鈕的_events
: clickTest : Array(1) 0 : ? invoker() length :
發(fā)現(xiàn)clicktest還在。這就是問(wèn)題所在了。
那么我們?cè)撊绾稳セ乇苓@樣的問(wèn)題呢,還是應(yīng)從diff的比對(duì)方式來(lái)解決問(wèn)題,還是看代碼。
function sameVnode (a, b) { return ( a.key === b.key && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) }
也就是對(duì)diff來(lái)說(shuō),所謂相同的第一判定原則是key。
key也是react引入diff時(shí)添加的一個(gè)屬性,用來(lái)判斷前后vdom樹(shù)上是否為統(tǒng)一元素(注意是同級(jí)關(guān)系上),所以我們只需要在代碼上加key,就可以避免這個(gè)問(wèn)題
<Button key="1" v-if="disabled" @clickTest="handleClick">可點(diǎn)擊</Button> <Button key="2" v-else>不可點(diǎn)擊</Button>
這樣,我們?cè)邳c(diǎn)擊按鈕時(shí),就不會(huì)再出彈框了。
key的作用很廣泛,當(dāng)我們?cè)诒闅v數(shù)組生成dom時(shí),添加一個(gè)可確定的唯一id(注意不應(yīng)該用數(shù)組索引),會(huì)優(yōu)化我們的比對(duì)效率以及更少的操作dom。我們也會(huì)在某個(gè)div上添加key以確保他不會(huì)因?yàn)樾值茉氐淖儎?dòng)而被重新渲染(這類div一般會(huì)被綁定react或vue以外的事件或動(dòng)作,如在這個(gè)div中生成了一個(gè)canvas等)。
那么除了在組件上加這種不必要key值以外,還有別的方法解決嗎?
有的,這里有一種很反vue但是類react的方式,就是把回調(diào)事件通過(guò)props的方式傳遞,向下面著這樣,
<Button v-if="disabled" :clickTest="handleClick">可點(diǎn)擊</Button> <Button v-else>不可點(diǎn)擊</Button> props: { 'clickTest': { type: Function } }, methods: { handleClick() { //this.$emit('clickTest') this.clickTest && this.clickTest() } }
雖然vue給了我們更方便的事件傳遞的方式,但props里是允許我們?nèi)鬟f任何類型的,我的期望是在真實(shí)的dom上或者在公共組件的入口處以外的地方,都是通過(guò)props的方式來(lái)傳遞結(jié)果的。雖然這種方式很不vue,而且也享受不到v-on給我們帶來(lái)的遍歷,但是這樣確實(shí)可以減少不必要的麻煩。
當(dāng)然既然用了vue,更好的利用vue給我們帶來(lái)的便利也很重要,所以對(duì)于這種很少會(huì)出現(xiàn)的麻煩,我們有一個(gè)預(yù)期,并可以快速定位并修復(fù)問(wèn)題,就可以了。
關(guān)于vue組件間怎么實(shí)現(xiàn)事件傳遞問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。