博客 / 詳情

返回

vue3使用mitt事件管理

註冊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的頁面裏,在onBeforeUnmountonUnmounted生命週期內卸載對應的監聽,比如這樣:

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 或類似的事件總線),監聽器的識別基於函數引用,而不是函數名或內容。
使用具名函數時:

  1. eventTest是一個固定的函數引用
  2. 每個組件實例都有自己的 eventTest 函數實例
  3. 使用 mitt.off(eventName, event) 可以精確移除特定的監聽器函數
  4. 不會影響其他組件註冊的監聽器

具名函數與匿名函數的區別

// 匿名函數 - 每次都是不同的引用
() => {} === () => {} // false

// 具名函數 - 在同一作用域內是相同引用
function handler() {}
handler === handler // true

const handler2 = () => {}
handler2 === handler2 // true

匿名函數:每次創建都是不同的函數實例,無法精確指定要移除哪一個
具名函數:在同一作用域內保持相同的引用,可以精確指定要移除的監聽器

這就是為什麼使用具名函數並配合 mitt.off(eventName, event)可以只移除當前組件的監聽器,而不會影響其他組件的原因。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.