事件总线Mitt使用非常简单,本篇随笔介绍在Vue3+TypeScript 前端项目中使用的一些场景和思路。我们在Vue 的项目中,经常会通过emits 触发事件来通知组件或者页面进行相应的处理,不过我们使用事件总线Mitt来操作一些事件的处理,也是非常方便的。
- import mitt from 'mitt'
- const emitter = mitt()
- // 订阅一个具体的事件
- emitter.on('foo', e => console.log('foo', e) )
- // 订阅所有事件
- emitter.on('*', (type, e) => console.log(type, e) )
- // 发布一个事件
- emitter.emit('foo', { a: 'b' })
- // 根据订阅的函数来取消订阅
- function onFoo() {}
- emitter.on('foo', onFoo) // listen
- emitter.off('foo', onFoo) // unlisten
-
- // 只传一个参数,取消订阅同名事件
- emitter.off('foo') // unlisten
-
- // 取消所有事件
- emitter.all.clear()
而我们如果在Vue3 + TypeScript 环境中使用的话,就需要类型化事件的类型,已达到强类型的处理目的。
- import mitt from "mitt";
- type Events = {
- foo: string;
- bar: number;
- };
- // 提供泛型参数让 emitter 能自动推断参数类型
- const emitter = mitt<Events>();
- // 'e' 被推断为string类型
- emitter.on("foo", (e) => {
- console.log(e);
- });
- // ts error: 类型 string 的参数不能赋值给类型 'number' 的参数
- emitter.emit("bar", "xx");
- // ts error: otherEvent 不存在与 Events 的key中
- emitter.on("otherEvent", () => {
- //
- });
在前端项目使用的时候,我们在utils/mitt.ts中定义默认导出的mitt对象,如下代码所示。
- // utils/mitt.ts
- import mitt, { Emitter } from 'mitt';
- // 类型
- const emitter: Emitter<MittType> = mitt<MittType>();
- // 导出
- export default emitter;
在其中的MittType类型,可以单独文件放置TypeScript的预定义文件目录中,如types/mitt.d.ts
而我们在使用的时候,直接导入该对象就可以了,如下代码所示。
- declare type MittType<T = any> = {
- openSetingsDrawer?: string;
- restoreDefault?: string;
- setSendColumnsChildren: T;
- .................. //省略其他事件类型
- noticeRead: number; // 消息已读事件
- lastAddParentId?: string | number;//新增记住最后的父信息
- };
例如我们定义一个更新和记住父菜单的Mitt 事件,在页面加载完毕的时候监听事件,在页面退出的时候关闭事件即可,如下代码所示是在菜单列表页面中处理的。
- <script lang="ts" setup name="sysMenu">
- import { onMounted, onUnmounted, reactive, ref } from 'vue';
- import mittBus from '/@/utils/mitt';
- ......
- onMounted(async () => {
- handleQuery();
- mittBus.on('submitRefresh', () => {
- handleQuery();
- });
- mittBus.on('lastAddParentId', (pid) => {
- state.lastAddParentId = pid as string;//记住最后的父菜单ID
- });
- });
- onUnmounted(() => {
- mittBus.off('submitRefresh');
- mittBus.off('lastAddParentId');
- });
- </script>
在新增菜单的时候我们触发对应刷新事件 submitRefresh,以及触发选择的父记录ID的事件 lastAddParentId,这样就可以做相应的处理了。
例如在菜单的编辑子控件页面中,我们触发对应的事件逻辑代码如下所示。
- // 关闭弹窗
- const closeDialog = () => {
- mittBus.emit('submitRefresh');
- state.isShowDialog = false;
- };
- // 提交
- const submit = () => {
- ruleFormRef.value.validate(async (valid: boolean) => {
- if (!valid) return;
- if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
- await menuApi.update(state.ruleForm);
- } else {
- await menuApi.add(state.ruleForm);
- //记住最后的菜单
- mittBus.emit('lastAddParentId', state.ruleForm.pid);
- }
- closeDialog();
- });
- };
如果为了减少每次重复的导入mitt,也可以把它全局挂载到变量中,统一入口进行访问,详细可以参考随笔《在基于vue-next-admin的Vue3+TypeScript前端项目中,为了使用方便全局挂载的对象接口》处理即可。
- const $u: $u_interface = {
- message,
- test,
- util,
- date,
- crypto,
- base64,
- $t: i18n.global.t,
- fun: commonFunction(),
- cloneDeep,
- debounce,
- throttle,
- mitt
- };
- //安装$u组件到app上
- import type { App } from 'vue';
- export default {
- install(app: App<Element>) {
- // 挂载全局
- app.config.globalProperties.$u = $u;
- }
- };
