實現一個EventBus
vue組件間通信方式有多種,對於父子組件,最常用的是使用props
和emit
進行通信,對於非父子組件,我們會採用EventBus進行通信,具體如何操作先看如下demo:
// utils.js
// 創建一個Vue實例,作爲EventBus
import Vue from 'vue'
export const EventBus = new Vue()
有三個組件:Parent、Child、GrandChild,它們之間嵌套,即Parent > Child > GrandChild,利用EventBus我們可以很輕鬆的實現GrandChild與Parent組件之間的通信
// Parent.vue
<template>
<div>
<div>{{ msg }}</div>
<child></child>
</div>
</template>
<script>
// 引入EventBus
import { EventBus } from '../utils'
import Child from './Child'
export default {
name: 'parent',
components: {
Child
},
data() {
return {
msg: 'hello'
}
},
mounted() {
// 在mounted中綁定事件
EventBus.$on('hello', this.handleHello)
},
methods: {
handleHello(data) {
this.msg = 'i am from ' + data
}
}
}
</script>
// Child.vue
<template>
<div>
<div>i am child</div>
<grand-child></grand-child>
</div>
</template>
<script>
import GrandChild from './GrandChild'
export default {
name: 'child',
components: {
GrandChild
}
}
</script>
// GrandChild.vue
<template>
<div>
<div @click="handleClick">grandChild</div>
</div>
</template>
<script>
// 引入EventBus
import { EventBus } from '../utils'
export default {
name: 'grand-child',
methods: {
handleClick() {
// 通過EventBus觸發hello事件
EventBus.$emit('hello', 'grandChild')
}
}
}
</script>
效果如下:
這樣在GrandChild
組件中點擊時Parent
組件中顯示的msg
會發生變化,上面demo中就是通過創建一個Vue實例實現了EventBus,利用了Vue爲我們提供的事件綁定($on
)、事件觸發($emit
)等方法來實現通信。
Vue內部實現了EventBus,可以閱讀vue源碼(core/instance/events.js)查看vue中事件綁定、事件觸發、事件解除等是如何實現的。EventBus是典型的發佈訂閱模式,即先訂閱事件,通過鍵值對的形式存儲訂閱的事件名和對應回調,當事件觸發時,會依次觸發訂閱該事件的所有回調,爲了加深對EventBus的理解,可以自己動手實現一個簡單的EventBus,具體如下:
// 定義EventBus構造函數,訂閱事件是通過鍵值對的形式存儲,在這兒使用Map來儲存,因爲Map比普通對象更強大
function EventBus() {
let that = this
that._events = new Map()
}
// EventBus具有事件綁定、事件觸發、事件解除的方法
// 事件綁定方法on
EventBus.prototype.on = function (type, fn) {
let that = this
// 根據事件名type,獲取事件對應的回調方法
let handler = that._events.get(type)
if(!handler){ // 如果handler不存在,直接調用set方法添加
that._events.set(type, fn)
} else if(typeof handler === 'function'){ // 如果handler存在,且handler是個函數,此時需要將handler變成一個數組來存放fn
that._events.set(type, [handler, fn])
} else { // handler爲數組時,直接通過push方法添加fn
handler.push(fn)
}
}
// 事件觸發方法emit
EventBus.prototype.emit = function (type, ...args){
let that = this
let handler = that._events.get(type)
if(!handler){ // 如果handler不存在,直接返回
return
} else if(Array.isArray(handler)){ // handler類型爲數組,則遍歷handler,讓其中的每個回調都執行一遍
for(let i = 0; i < handler.length; i++){
handler[i].apply(that, args) // 執行事件對應的回調
}
} else { // handler類型不是數組,說明當前事件只有一個回調方法,則直接調用
handler.apply(that, args)
}
}
// 事件解除方法off,值得注意的是匿名函數是無法解除綁定的,如果想解除,需要使用具名函數
EventBus.prototype.off = function (type, fn){
let that = this
let handler = that._events.get(type)
if(!handler){ // hanlder不存在時直接返回
return
} else if(typeof handler === 'function'){ // handler類型爲function,說明當前事件只有一個回調方法,則直接調用delete刪除
that._events.delete(type, fn)
} else { // handler類型爲數組時,需要在handler中找到當前回調函數fn並刪除
let position = -1 // 用於存儲fn在handler中的索引值
for(let i = 0; i < handler.length; i++){
if(handler[i] === fn){
position = i
}
}
if(position !== -1){ // 找到fn在handler中的索引值後,直接在handler中刪除
handler.splice(position, 1)
if(handler.length === 1){ // 當handler中只剩一個回調函數時,將handler從數組轉換爲函數
that._events.set(type, handler[0])
}
}
}
}
EventBus測試:
let eb = new EventBus()
function f1(name) {
console.log('hello, ' + name)
}
function f2(name) {
console.log('i am ' + name)
}
eb.on('hello', f1)
eb.on('hello', f2)
eb.emit('hello', 'Ryan') // 'hello, Ryan' 'i am Ryan'
eb.off('hello', f1)
eb.off('hello', f2)
eb.emit('hello', 'Ryan') // ''
實例化一個EventBus
,在eb
上添加hello
事件,hello
事件有兩個回調方法f1
和f2
,當觸發hello
事件時,會執行f1
和f2
兩個回調函數,解除hello
事件的f1
和f2
回調方法,再次觸發hello
事件,則無結果返回;
至此,一個簡單的EventBus就實現完成了。
參考文獻;
[1] 如何實現一個Event