探索前端跨組件通信:EventBus 在 Vue 和 React 中的應用
本文作者系 360 奇舞團前端開發工程師
EventBus 簡介
事件總線(Event Bus) 是一種用於組件間通信的模式,通常用於解決組件之間的解耦和簡化通信的問題。在前端框架中,如 Vue.js,事件總線是一個常見的概念。基本上,事件總線是一個能夠觸發和監聽事件的機制,使得組件能夠在不直接依賴彼此的情況下進行通信。事件總線可以是一個全局的單例對象,也可以是一個基於發佈 - 訂閱模式的實現。
設計模式
在軟件架構中,發佈 / 訂閱(Publish–subscribe pattern)是一種消息範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者),而是將發佈的消息分爲不同的類別,無需瞭解哪些訂閱者(如果存在)。同樣,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(如果存在)。
訂閱 - 發佈模式(Publish-Subscribe Pattern)是一種軟件設計模式,也屬於行爲型模式之一。它定義了一種對象間一對多的依賴關係,當一個對象的狀態發生變化時,所有依賴於它的對象都會得到通知並自動更新。這種模式降低了對象之間的直接耦合,使得系統更加靈活。該模式包含兩個主要角色:
-
發佈者(Publisher): 負責發佈(廣播)消息或事件的對象。當發佈者的狀態發生變化時,它會通知所有已訂閱的對象。
-
訂閱者(Subscriber): 訂閱發佈者的消息或事件的對象。訂閱者通過註冊自己的回調函數(或觀察者)來接收發布者的通知。
具體實現步驟如下:
-
發佈者維護一個訂閱者列表(數組),用於存儲所有訂閱了它的對象。
-
訂閱者向發佈者註冊自己的回調函數(或觀察者)。
-
當發佈者的狀態發生變化時,它會遍歷訂閱者列表,調用每個訂閱者的回調函數,通知它們狀態的變化。
EventBus 在前端(Vue 中)的使用
- 創建事件總線:
// event-bus.js
import Vue from 'vue';
// 創建一個新的Vue實例作爲事件總線
const EventBus = new Vue();
// 導出該實例,以便在應用程序中的其他地方使用
export default EventBus;
- 組件 A:
<!-- ComponentA.vue -->
<template>
<div>
<button @click="emitEvent">觸發事件</button>
</div>
</template>
<script>
import EventBus from './event-bus.js';
export default {
methods: {
emitEvent() {
// 使用事件總線觸發名爲 'custom-event' 的事件,並傳遞數據
EventBus.$emit('custom-event', '這是傳遞的數據');
}
}
}
</script>
- 組件 B:
<!-- ComponentB.vue -->
<template>
<div>
<p>{{ eventData }}</p>
</div>
</template>
<script>
import EventBus from './event-bus.js';
export default {
data() {
return {
eventData: ''
};
},
mounted() {
// 在組件創建時,通過事件總線監聽 'custom-event' 事件
EventBus.$on('custom-event', eventData => {
// 更新組件的數據
this.eventData = eventData;
console.log('收到事件,數據爲:', eventData);
});
}
}
</script>
ComponentA 組件通過點擊按鈕觸發了一個名爲 custom-event 的事件,並傳遞了一些數據。ComponentB 組件在創建時通過事件總線監聽了這個事件,並在事件發生時更新了組件的數據。注意:使用事件總線時需要注意組件的生命週期,確保在不再需要監聽事件的組件被銷燬時取消事件監聽,以避免潛在的內存泄漏。
- 銷燬
beforeDestroy() {
// 在組件銷燬前取消事件監聽
EventBus.$off('custom-event', this.eventBusListener);
}
EventBus 在前端(React 中)的使用
在 React 中,沒有像 Vue 中的事件總線那樣的直接內置機制。React 通常使用 props 和回調函數來實現組件之間的通信。然而,如果你的應用需要在不適用 props 傳遞的情況下進行全局事件的訂閱和發佈,可以使用第三方庫,比如 eventemitter3 或者 Redux。以下是使用 Event Emitter 的一個簡單示例:
- 安裝 eventemitter3
npm install eventemitter3
- 創建全局的事件管理器
// eventBus.js
import { EventEmitter } from 'eventemitter3';
const eventBus = new EventEmitter();
export default eventBus;
- 引入這個事件總線訂閱和發佈事件:
// ComponentA.jsx
import React from 'react';
import eventBus from './eventBus';
class ComponentA extends React.Component {
emitEvent = () => {
eventBus.emit('custom-event', '這是傳遞的數據');
};
render() {
return (
<div>
<button onClick={this.emitEvent}>觸發事件</button>
</div>
);
}
}
export default ComponentA;
// ComponentB.jsx
import React, { useState, useEffect } from 'react';
import eventBus from './eventBus';
const ComponentB = () => {
const [eventData, setEventData] = useState('');
useEffect(() => {
const eventBusListener = (data) => {
setEventData(data);
console.log('收到事件,數據爲:', data);
};
eventBus.on('custom-event', eventBusListener);
return () => {
// 在組件卸載時取消事件監聽
eventBus.off('custom-event', eventBusListener);
};
}, []);
return (
<div>
<p>{eventData}</p>
</div>
);
};
export default ComponentB;
- 注意:在組件卸載 eventBus.off('custom-event', eventBusListener); 時取消事件監聽以避免潛在的內存泄漏。
使用 EventBus 優缺點
優點:
-
解耦組件: 事件總線能夠實現組件之間的解耦,使得它們不需要直接引用或依賴彼此,提高了代碼的靈活性和可維護性。
-
簡化通信: 對於一些簡單的通信需求,事件總線提供了一種相對簡單的方式,避免了通過 props 和回調函數傳遞數據時的繁瑣操作。
-
全局通信: 事件總線通常是全局性的,能夠在整個應用程序中的任何地方進行通信,適用於全局狀態的傳遞和應用的整體控制。
-
跨組件通信: 事件總線可以方便地實現非父子組件之間的通信,而不需要在組件之間建立直接的關聯。
缺點:
-
全局狀態管理: 使用事件總線可能引入全局狀態,導致應用狀態變得難以追蹤和理解,特別是在大型應用中。
-
難以調試: 全局性的事件監聽和觸發可能使得追蹤代碼執行流程和調試變得更加困難,尤其是在複雜的應用場景下。
-
潛在的性能問題: 大量的全局事件監聽和觸發可能導致性能問題,尤其是在頻繁觸發事件的情況下。
-
不明確的數據流向: 使用事件總線時,數據的流向相對不明確,可能增加代碼的複雜性,使得應用程序的數據流變得更加難以理解。
-
安全性問題: 由於事件總線是全局的,可能存在安全風險,例如某個組件監聽了不應該被其它組件觸發的敏感事件。
總結
綜合考慮,對於小型應用或簡單的場景,事件總線是一個方便的工具。但在大型應用或需要更嚴格狀態管理和調試的情況下,可能需要考慮使用更復雜的狀態管理工具,如 Vuex 或 Redux。使用事件總線時,需要謹慎使用,避免濫用全局狀態和事件。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/wKiwjRmSSh0G9Tsd3CGJbA