vue中總線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')
  }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM