實現一個EventBus

實現一個EventBus

vue組件間通信方式有多種,對於父子組件,最常用的是使用propsemit進行通信,對於非父子組件,我們會採用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事件有兩個回調方法f1f2,當觸發hello事件時,會執行f1f2兩個回調函數,解除hello事件的f1f2回調方法,再次觸發hello事件,則無結果返回;

至此,一個簡單的EventBus就實現完成了。

參考文獻;

[1] 如何實現一個Event

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章