
摘要
在 HarmonyOS 的开发过程中,随着项目模块的不断增加,页面与组件之间的直接调用变得不再现实。如何高效、解耦地实现模块间通信,成为了开发者关注的重点。ArkTS 提供了多种方案,其中事件总线是一种简单、轻便、扩展性强的方式,适用于大部分跨模块通信场景。
引言
在日常开发中,我们常常会遇到这样的情况:A 模块的某个动作需要影响 B 模块的状态,但这两个模块又没有直接的父子或引用关系。如果你还在用全局变量、静态方法传值,那其实风险挺大 ------ 不仅维护难度高,而且逻辑容易混乱。为了解耦模块之间的通信,我们可以借助事件总线(EventBus)。
事件总线是一种发布-订阅模式,它允许我们在不同组件或模块之间传递消息,而不需要彼此知道对方的存在。
跨模块通信的实现方式
使用事件总线封装通信逻辑
我们先来创建一个全局的事件总线类 EventBus.ts
,它是整个通信机制的核心。
ts
// EventBus.ts
export class EventBus {
private static instance: EventBus;
private events: { [key: string]: Function[] } = {};
private constructor() {}
static getInstance() {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
on(event: string, callback: Function) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event: string, data?: any) {
if (this.events[event]) {
this.events[event].forEach((callback) => callback(data));
}
}
}
这个类使用了单例模式,确保事件中心只有一个实例,并通过 on
方法监听事件,通过 emit
方法广播事件。
主页面:发送事件的模块
我们在主页面 MainPage.ets
中使用按钮点击来发送一个事件:
ts
// MainPage.ets
import { EventBus } from './EventBus';
@Entry
@Component
struct MainPage {
build() {
Column() {
Button('Send Event')
.onClick(() => {
EventBus.getInstance().emit('message', 'Hello from MainPage');
});
}.padding(20);
}
}
点击按钮时,通过 emit
方法发送了一个名为 message
的事件,并携带数据 Hello from MainPage
。
接收页面:监听并响应事件
在另一个页面 SecondaryPage.ets
中,我们来接收这个事件:
ts
// SecondaryPage.ets
import { EventBus } from './EventBus';
@Entry
@Component
struct SecondaryPage {
private message: string = 'No message yet';
build() {
// 注册事件监听器
EventBus.getInstance().on('message', (data) => {
this.message = data;
});
Column() {
Text(this.message)
.fontSize(20);
}.padding(20);
}
}
SecondaryPage
中注册了 message
事件的监听器,每当有消息发出时,就会更新页面上的 message
。
实际应用场景举例
场景一:登录成功后通知首页刷新用户信息
代码示例:
ts
// LoginPage.ets
EventBus.getInstance().emit('loginSuccess', { username: '张三' });
// HomePage.ets
EventBus.getInstance().on('loginSuccess', (data) => {
this.username = data.username;
});
说明:
用户登录成功后不需要手动跳转或回调处理,首页收到 loginSuccess
事件后即可自动更新展示用户名或用户信息。
场景二:设置页面修改主题,通知多个模块更新 UI 风格
代码示例:
ts
// SettingsPage.ets
EventBus.getInstance().emit('themeChanged', 'dark');
// AnyPage.ets
EventBus.getInstance().on('themeChanged', (theme) => {
this.currentTheme = theme;
});
说明:
通过事件总线广播主题变更事件,多个页面只需要注册监听就能自动响应,无需层层传递参数。
场景三:播放器状态通知多个页面同步更新
代码示例:
ts
// Player.ets
EventBus.getInstance().emit('playStatus', 'paused');
// FooterWidget.ets / LyricsPage.ets / ControlPanel.ets
EventBus.getInstance().on('playStatus', (status) => {
this.playStatus = status;
});
说明:
当播放器状态改变后,通过事件总线通知相关组件更新播放图标、歌词状态、控制栏等,做到统一同步更新。
QA 环节
Q1: 为什么不直接使用全局变量或传参来实现?
全局变量虽然方便,但维护困难、安全性差,容易导致模块耦合度过高。事件总线属于发布-订阅模式,发送者和接收者解耦,维护更清晰,扩展性强。
Q2: 如果页面被销毁了,事件监听器还存在吗?
当前实现中,监听器会一直保留在内存中。如果你在组件卸载后不需要再接收事件,建议添加手动移除监听器的功能(可扩展 off
方法)来避免内存泄漏。
Q3: 事件可以传复杂对象吗?
可以,emit(event: string, data: any)
中的 data
可以是字符串、对象、数组甚至函数,只要发送和接收逻辑一致即可。
总结
事件总线是一种非常实用的跨模块通信机制,特别适用于页面之间、组件之间没有直接引用关系时的消息传递。在 ArkTS 的实际开发中,它既轻便,又高效,能大大减少模块之间的耦合,提高代码的可维护性。
如果你在做分布式模块化开发、复杂页面联动,或者需要组件间通信,不妨试试这个思路。
如需后续支持场景(例如带 once
、off
功能的事件总线),也可以继续优化升级。