動態組件中用匯流排Bus的坑 在我們的項目總難免會遇到用動態組件,這裡就拿 "vue官方的例子" 為例,我們欲在組件中添加匯流排bus(其實官方推薦的vuex更好用,但是有時候我們只需要傳一個小狀態,不需要用vuex),首先要mian.js 中創建一個匯流排Bus(當然這裡一般要把Bus封裝一下放在一個單 ...
動態組件中用匯流排Bus的坑
在我們的項目總難免會遇到用動態組件,這裡就拿vue官方的例子為例,我們欲在組件中添加匯流排bus(其實官方推薦的vuex更好用,但是有時候我們只需要傳一個小狀態,不需要用vuex),首先要mian.js 中創建一個匯流排Bus(當然這裡一般要把Bus封裝一下放在一個單獨的js中,這裡單純只是為了演示,就在main.js中創建一個全局的EventBus)
import Vue from 'vue'
import App from './App'
import router from './router'
window.EventBus = new Vue()
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
然後我們在動態組件Tabhome中寫個按鈕觸發emit事件,在觸發的時候把我們想要傳的值一併帶過去。
<template>
<div>
<div>
<button @click="handleClick">觸發</button>
</div>
</div>
</template>
<script>
export default {
name: 'TabHome',
data () {
return {
msg: 'home data '
}
},
methods: {
handleClick () {
window.EventBus.$emit('getData', this.msg)
}
}
}
</script>
然後我們在我們想在接受值的地方監聽觸發的這個函數,我這裡拿TabPosts來監聽Tabhome中觸發的函數,註意這兩個組件是動態組件,不是通過路由切換的,監聽組件如下:
<template>
<div>
{{post}}
</div>
</template>
<script>
export default {
name: 'TabPosts',
data () {
return {
post: 'tabposts',
}
},
methods: {
getData (msg) {
this.post = msg
}
},
mounted () {
window.EventBus.$on('getData', (msg) => this.getData(msg))
}
}
</script>
我們期望的結果當然是我們在tabhome中點擊了按鈕之後,當我們切換到TabPosts組件的時候,TabPosts中的值已經發生了改變,也就是從tabhome中傳過來的值,但是情況遠非我們想的這麼簡單,在監聽函數中添加console你就會發現,第一次點擊按鈕,並切換到TabPosts組件的時候,不會列印任何東西,也就是沒有觸發mounted鉤子。當你切回去tabhome組件再次點擊按鈕,然後再回到TabPosts組件,發現控制台有輸出,但是隨著來回切換次數的增多,控制台每次列印的數量也會隨著你切換的次數一次遞增,但是數據發生了改變,視圖卻沒有改變。
這是為什麼呢?這就是動態組件的坑,因為我用的是生命周期的鉤子函數,監聽函數要在觸發函數之前存在 ,不然當然監聽不到了。問題就出在生命周期函數這裡。所以我們來在兩個組件中加上所有的生命周期鉤子併在裡面輸出識別信息。tabhome組件如下:
<template>
<div>
<div>
<button @click="handleClick">觸發</button>
</div>
</div>
</template>
<script>
export default {
name: 'TabHome',
data () {
return {
msg: 'home data '
}
},
methods: {
handleClick () {
window.EventBus.$emit('getData', this.msg)
}
},
beforeCreate () {
console.log('A beforecreate')
},
created () {
console.log('A created')
},
beforeMount () {
console.log('A beforemount')
},
mounted () {
console.log('A mounted')
},
beforeUpdate () {
console.log('A before update')
},
updated () {
console.log('A updated')
},
beforeDestroy () {
console.log('A before destroy')
},
destroyed () {
console.log('A beforecreate')
}
}
</script>
TabPosts組件如下(為了在控制台明顯區分,在這裡給TabPost組件列印的東西加上黃色的背景色):
<template>
<div>
{{post}}
<router-link to="/TabHome">return</router-link>
</div>
</template>
<script>
export default {
name: 'TabPosts',
data () {
return {
post: 'tabposts',
number: 0
}
},
methods: {
getData (msg) {
this.post = msg
}
},
beforeCreate () {
console.log('%c%s',
'background: yellow;',
'B beforecreate')
},
created () {
console.log('%c%s',
'background: yellow;',
'B created')
},
beforeMount () {
console.log('%c%s',
'background: yellow;',
'B beforemount')
},
mounted () {
console.log('%c%s',
'background: yellow;',
'B mounted')
window.EventBus.$on('getData', (msg) => this.getData(msg))
},
beforeUpdate () {
console.log('%c%s',
'background: yellow;',
'B before update')
},
updated () {
console.log('%c%s',
'background: yellow;',
'B updated')
},
beforeDestroy () {
console.log('%c%s',
'background: yellow;',
'B before destroy')
},
destroyed () {
console.log('%c%s',
'background: yellow;',
'B beforecreate!')
}
}
</script>
<style scoped>
</style>
然後我們測試從動態組件tabhome到TabPosts組件的這個過程中控制台會列印出什麼,結果如下圖:
我們會發現,A組件(即是tabhome組件)和B組件(即是TabPosts組件)兩個的生命周期函數沒有交集也就是說觸發emit的時候並沒有監聽到,所以視圖不會改變。至於在動態組件中來回切換會增加觸發次數,根據前人的經驗,應該是在監聽組件B中的beforeDestroy中添加EventBus.$off函數就好了,但是會發下加了這個off之後,就不會觸發$on的監聽函數了,至於為什麼不會監聽函數這其中的原因我也不太懂。
目前還沒找到動態組件中實現匯流排Bus的好方法,大佬們有好方法歡迎指正!
組件之間的Bus匯流排傳值
因為動態組件之間的坑,我放棄了用動態組件,改用路由切換的兩個組件進行傳值。在路由的index.js中加入路由信息
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/Home'
import TabHome from '@/pages/Dynamic-component/components/TabHome'
import TabPosts from '@/pages/Dynamic-component/components/TabPosts'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
}, {
path: '/tabhome',
name: 'TabHome',
component: TabHome
}, {
path: '/TabPosts',
name: 'TabPosts',
component: TabPosts
}
]
})
但是這其中也有坑,我們由A(即tabhome)組件觸發EventBus.$emit 函數,讓B(即TabPosts)組件監聽EventBus.$on,一般觸發函數都會放到click函數中,也就是哪個事件需要就放到哪裡,本例子放到click事件中。監聽函數一般放到created或者mounted中,這裡我放到了mounted中。
A(tabhome)組件代碼如下:
<template>
<div>
<div>
<button @click="handleClick">觸發</button>
</div>
</div>
</template>
<script>
export default {
name: 'TabHome',
data () {
return {
msg: 'home data '
}
},
methods: {
handleClick () {
window.EventBus.$emit('getData', this.msg)
this.$router.push('/TabPosts')
}
},
beforeCreate () {
console.log('A beforecreate')
},
created () {
console.log('A created')
},
beforeMount () {
console.log('A beforemount')
},
mounted () {
console.log('A mounted')
},
beforeUpdate () {
console.log('A before update')
},
updated () {
console.log('A updated')
},
beforeDestroy () {
console.log('A before destroy')
},
destroyed () {
console.log('A beforecreate')
}
}
</script>
B(TabPosts)組件的代碼如下:
<template>
<div>
{{post}}
<router-link to="/TabHome">返回</router-link>
</div>
</template>
<script>
export default {
name: 'TabPosts',
data () {
return {
post: 'tabposts',
number: 0
}
},
methods: {
getData (msg) {
this.post = msg
}
},
beforeCreate () {
console.log('%c%s',
'background: yellow;',
'B beforecreate')
},
created () {
console.log('%c%s',
'background: yellow;',
'B created')
},
beforeMount () {
console.log('%c%s',
'background: yellow;',
'B beforemount')
},
mounted () {
console.log('%c%s',
'background: yellow;',
'B mounted')
window.EventBus.$on('getData', (msg) => this.getData(msg))
},
beforeUpdate () {
console.log('%c%s',
'background: yellow;',
'B before update')
},
updated () {
console.log('%c%s',
'background: yellow;',
'B updated')
},
beforeDestroy () {
console.log('%c%s',
'background: yellow;',
'B before destroy')
},
destroyed () {
console.log('%c%s',
'background: yellow;',
'B beforecreate!')
}
}
</script>
<style scoped>
</style>
結果我們按照這個代碼運行總是不成功,沒有我們想要的效果,上面的代碼我加了所有的生命周期的鉤子函數,我們從A的按鈕切換到B組件,註意留意控制台,當我們點擊按鈕通通過路由切換到B組件的時候,生命周期函數的變化,我們會發現如下的結果。
我們發現,在A銷毀之前,B組件的beforeCreate ,created,和beforeMount這三個鉤子函數先觸發,之後才是A組件的銷毀鉤子的觸發,因為匯流排Bus要求要先有監聽在觸發,才能成功監聽,所以我們只能在A組件的beforeDestroy或者destroyed這兩個生命周期鉤子中觸發函數$emit,同理也只能在B組中的beforeCreate ,created,和beforeMount這三個鉤子函數中監聽$on。
//tabhome (A)組件中在beforeDestroy中觸發
beforeDestroy () {
console.log('A before destroy')
window.EventBus.$emit('getData', this.msg)
}
//在TabPosts中的created中監聽
created () {
console.log('%c%s',
'background: yellow;',
'B created')
console.log(1)
window.EventBus.$on('getData', (msg) => this.getData(msg))
}
這樣我們想要的功能就實現了,實際動手做的細心的同學會發現:還是有之前重覆觸發的問題,還是會隨著切換次數的增加而使監聽函數觸發的次數增加,解決這個問題就簡單了。在我們用匯流排傳值的時候要記得關閉監聽,在B組件中的destroyed鉤子中增加EventBus.$off方法即可,至此就沒問題了。
//TabPosts組件
destroyed () {
console.log('%c%s',
'background: yellow;',
'B beforecreate!')
window.EventBus.$off('getData')
}