vue3-mitt-event-bus
test12026-10-2619 次阅读
为什么不直接用状态管理
父子、跨层级的数据流动用 provide/inject 或 Pinia 都很顺。但有些事件本质是一次性的通知:登录成功、购物车变化、全局 toast,它们不是长期状态,硬塞进 store 反而让状态变脏。一个轻量事件总线更适合这类场景,而 Vue 3 已移除内置的 $on/$off,社区普遍用 mitt 接手。
封装一个带类型的 emitter
mitt 支持泛型,把事件名和载荷类型写进一个接口,拼错事件名或传错字段都会在编译期报错。
import mitt from "mitt";
type Events = {
"user:login": { id: number; name: string };
"cart:change": number;
"toast:show": { message: string; type: "success" | "error" };
};
export const emitter = mitt<Events>();
在组件里发布与订阅
订阅一定要在组件卸载时 off 掉,否则组件销毁后回调仍在,会访问到已释放的引用。
import { onMounted, onUnmounted } from "vue";
import { emitter } from "./emitter";
onMounted(() => {
emitter.on("toast:show", showToast);
});
onUnmounted(() => {
emitter.off("toast:show", showToast);
});
function showToast(payload: { message: string; type: "success" | "error" }) {
console.log(payload.message);
}
// 任意模块里触发,无需关心谁在监听
function login() {
emitter.emit("user:login", { id: 1, name: "alice" });
}
在组合式函数里自动清理
把 on/off 包一层类型安全的 helper,再放进 composable,组件只需声明自己关心哪个事件,卸载逻辑交给 onUnmounted。
import type { Handler } from "mitt";
import { onUnmounted } from "vue";
import { emitter, Events } from "./emitter";
function on<K extends keyof Events>(
type: K,
handler: (payload: Events[K]) => void
) {
emitter.on(type, handler as Handler<Events[keyof Events]>);
}
function off<K extends keyof Events>(
type: K,
handler: (payload: Events[K]) => void
) {
emitter.off(type, handler as Handler<Events[keyof Events]>);
}
export function useEventBus<K extends keyof Events>(
type: K,
handler: (payload: Events[K]) => void
) {
on(type, handler);
onUnmounted(() => off(type, handler));
}
注意点
- 事件总线适合通知,不适合当全局状态用;需要读最新值的地方还是交给 store。
- mitt 默认只存一个 handler 引用,所以 off 时传的函数必须和 on 时是同一个,建议用命名函数而非内联箭头。
- 类型接口要随业务演进维护,事件越乱越要有文档。
T
test1
文章作者
为什么不直接用状态管理 父子、跨层级的数据流动用 provide/inject 或 Pinia 都很顺。但有些事件本质...
- 分类
- 技术
- 发布时间
- 2026-10-26
- 字数
- 约 1799 字
- 阅读
- 19 次
