注册mitt
main.ts注册
import { createApp } from 'vue';
import App from './App.vue';
// 导入mitt
import mitt from 'mitt';
const app = createApp(App);
app.config.globalProperties.$mitt = mitt();
app.mount('#app');封装管理器
// 定义事件类型映射
export const mittEvents = {
EVENT_TEST: 'event:test', // 测试事件
};
/**
* 卸载监听事件
* emit 只是触发事件,不涉及监听器的注册,所以只需要移除监听即可
* @param mitt mitt实例
* @param eventList 事件列表
*/
export const unloadMitt = (mitt: any, eventList: { eventName: string; callback?: (...args: any[]) => void }[]) => {
eventList.forEach(({ eventName, callback }) => {
if (callback) {
mitt.off(eventName, callback);
} else {
mitt.off(eventName);
}
});
};定义事件类型映射的作用是用来做全局的事件映射和处理,如果你在页面中随意的派发和监听emit事件,会导致事件混乱,意图不明确,所以我们需要统一在某个位置集中管理事件,至少知道对应的事件的作用是什么。
卸载监听事件的作用是用来卸载对某个事件的on监听,这是很关键的一步操作,如果不卸载的话可能会造成on事件的重复监听,下面我来详细说明一下:
1、组件内使用mitt.on,在销毁组件时需要卸载,根据具名函数卸载
2、页面级别的mitt.on也需要卸载,在onBeforeUnmount或者onUnmounted内卸载
全局监听与卸载
页面使用:
触发端
import { getCurrentInstance } from "vue;
import { mittEvents, unloadMitt } from '@/utils/mittManage';
const { proxy } = getCurrentInstance() as any;
const onTest = () => {
proxy.$mitt.emit(mittEvents.EVENT_TEST, { text: '发送事件' });
};接收端
import { getCurrentInstance } from "vue;
import { mittEvents } from '@/utils/mittManage';
const { proxy } = getCurrentInstance() as any;
proxy.$mitt.on(mittEvents.EVENT_TEST, (data: any) => {
console.log('接收事件', data);
});使用的时候需要注意,vue的页面都可以看成是一个组件,如果组件 渲染 -> 卸载 -> 渲染 (包括组件卸载、页面未keep-alive的跳转)后,mitt.on事件会被反复创建,如果你没有使用mitt.off卸载上一次的mitt.on监听的话,下一次渲染又会再次创建一个mitt.on监听,这样创建了多个mitt.on监听会导致同一个mitt.emit派发,mitt.on重复执行
正确的做法是在你mitt.on的页面里,在onBeforeUnmount或onUnmounted生命周期内卸载对应的监听,比如这样:
import { getCurrentInstance, onBeforeUnmount } from "vue;
import { mittEvents, unloadMitt } from '@/utils/mittManage';
const { proxy } = getCurrentInstance() as any;
proxy.$mitt.on(mittEvents.EVENT_TEST, (data: any) => {
console.log('接收事件', data);
});
// 卸载监听
onBeforeUnmount(() => {
unloadMitt(proxy.$mitt, [{eventName: mittEvents.EVENT_TEST}]);
});这样,当你的页面卸载掉后,当前的监听也卸载了。
注意:在上面的示例中,mitt.on是匿名函数监听,并且卸载时也仅提供了事件名,这会导致项目中所有对应的事件名一并卸载,如果你在不同的页面引入了相同的组件,在该组件内卸载了监听,会导致其它页面无法使用,因为监听被统一卸载掉了。
只卸载当前组件监听(推荐)
我们有时候需要把一个组件当成一个独立的环境,我们卸载监听时,只需要卸载当前组件的监听,不影响其它位置,此时可以用具名函数只移除当前组件的监听器,如下:
import { getCurrentInstance, onBeforeUnmount } from "vue;
import { mittEvents, unloadMitt } from '@/utils/mittManage';
const { proxy } = getCurrentInstance() as any;
const eventTest = (data: any) => {
console.log('接收事件', data);
proxy.$mitt.on(mittEvents.EVENT_TEST, eventTest);
// 卸载监听
onBeforeUnmount(() => {
unloadMitt(proxy.$mitt, [{eventName: mittEvents.EVENT_TEST, callback: eventTest}]);
});在事件系统中(如 mitt 或类似的事件总线),监听器的识别基于函数引用,而不是函数名或内容。
使用具名函数时:
eventTest是一个固定的函数引用- 每个组件实例都有自己的
eventTest函数实例 - 使用
mitt.off(eventName, event)可以精确移除特定的监听器函数 - 不会影响其他组件注册的监听器
具名函数与匿名函数的区别
// 匿名函数 - 每次都是不同的引用
() => {} === () => {} // false
// 具名函数 - 在同一作用域内是相同引用
function handler() {}
handler === handler // true
const handler2 = () => {}
handler2 === handler2 // true匿名函数:每次创建都是不同的函数实例,无法精确指定要移除哪一个
具名函数:在同一作用域内保持相同的引用,可以精确指定要移除的监听器
这就是为什么使用具名函数并配合 mitt.off(eventName, event)可以只移除当前组件的监听器,而不会影响其他组件的原因。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。