App中歉秫,我們經(jīng)常會(huì)需要實(shí)現(xiàn)廣播機(jī)制葛躏,用以跨頁(yè)面事件通知局齿。事件總線通常實(shí)現(xiàn)了訂閱模式剧劝,訂閱者模式包含了發(fā)布者和訂閱者兩種角色,可以通過事件總線來(lái)觸發(fā)事件和監(jiān)聽事件, 下面我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的全局事件總線抓歼,使用單例模式讥此。
核心原理就:?jiǎn)卫?+ Map<事件Key,訂閱者列表> + 列表遍歷谣妻,一個(gè)簡(jiǎn)單的實(shí)現(xiàn)代碼示例如下:
//訂閱者回調(diào)簽名
typedef void EventCallback(arg);
class EventBus {
//私有構(gòu)造函數(shù)
EventBus._internal();
//保存單例
static EventBus _singleton = EventBus._internal();
//工廠構(gòu)造函數(shù)
factory EventBus()=> _singleton;
//保存事件訂閱者隊(duì)列暂论,key:事件名(id),value: 對(duì)應(yīng)事件的訂閱者隊(duì)列
final _emap = Map<Object, List<EventCallback>?>();
//添加訂閱者
void on(eventName, EventCallback f) {
_emap[eventName] ??= <EventCallback>[];
_emap[eventName]!.add(f);
}
//移除訂閱者
void off(eventName, [EventCallback? f]) {
var list = _emap[eventName];
if (eventName == null || list == null) return;
if (f == null) {
_emap[eventName] = null;
} else {
list.remove(f);
}
}
//觸發(fā)事件拌禾,事件觸發(fā)后該事件所有訂閱者會(huì)被調(diào)用
void emit(eventName, [arg]) {
var list = _emap[eventName];
if (list == null) return;
int len = list.length - 1;
//反向遍歷取胎,防止訂閱者在回調(diào)中移除自身帶來(lái)的下標(biāo)錯(cuò)位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
}
//定義一個(gè)top-level(全局)變量,頁(yè)面引入該文件后可以直接使用bus
var bus = EventBus();
//頁(yè)面A中湃窍,監(jiān)聽登錄事件
bus.on("login", (arg) {
// do something
});
//登錄頁(yè)B中闻蛀,登錄成功后觸發(fā)登錄事件,頁(yè)面A中訂閱者會(huì)被調(diào)用
bus.emit("login", userInfo);
也可以用 dart:async 里的 Stream(流) 來(lái)實(shí)現(xiàn):
import 'dart:async';
class EventBus {
// 使用多訂閱流的形式您市,這種流可以有多個(gè)監(jiān)聽器監(jiān)聽(
final _streamController = StreamController.broadcast();
// 定義一個(gè)單例
static final EventBus _instance = EventBus._internal();
factory EventBus() {
return _instance;
}
EventBus._internal();
// 發(fā)布事件
void fire(event) {
_streamController.add(event);
}
// 訂閱事件
StreamSubscription on<T>(void Function(T) onData) {
return _streamController.stream.where((event) => event is T).listen(onData);
}
}
// 調(diào)用處:
var eventBus = EventBus();
// 發(fā)布一個(gè)事件
eventBus.fire(UserLoggedInEvent('Alice'));
// 在其他地方訂閱這個(gè)事件:
StreamSubscription subscription = eventBus.on<UserLoggedInEvent>((event) {
print('User logged in: ${event.username}');
});
//在合適的地方取消訂閱
subscription.cancel();
總體來(lái)說(shuō)觉痛,封裝方式1是一種基于回調(diào)函數(shù)的自定義事件機(jī)制,而封裝方式2是基于 Dart 內(nèi)置的 Stream 和 StreamController 的事件機(jī)制茵休。方式1相對(duì)簡(jiǎn)單薪棒,適用于基本的事件通知需求,而方式2更靈活榕莺、功能更強(qiáng)大俐芯,適用于復(fù)雜的場(chǎng)景,尤其是需要異步事件處理的情況钉鸯。
選擇建議:
如果項(xiàng)目簡(jiǎn)單吧史,不需要支持異步事件,且對(duì)事件系統(tǒng)的需求較為基礎(chǔ)唠雕,封裝方式1是一個(gè)簡(jiǎn)單有效的選擇贸营。
如果項(xiàng)目需要支持異步事件吨述、有復(fù)雜的事件過濾和管理需求,或者需要廣播模式支持多個(gè)監(jiān)聽器钞脂,封裝方式2是更為合適的選擇揣云。