在Vue3+TypeScript 前端專案中使用事件匯流排Mitt

2023-03-22 18:01:02

事件匯流排Mitt使用非常簡單,本篇隨筆介紹在Vue3+TypeScript 前端專案中使用的一些場景和思路。我們在Vue 的專案中,經常會通過emits 觸發事件來通知元件或者頁面進行相應的處理,不過我們使用事件匯流排Mitt來操作一些事件的處理,也是非常方便的。

Mitt 的GitHub官網地址如下所示:https://github.com/developit/mitt, 它的安裝和其他外掛一樣,我們不再贅述,只講述它的如何使用。

Mitt 具有以下優點:

  • 零依賴、體積超小,壓縮後只有200b
  • 提供了完整的typescript支援,能自動推匯出引數型別。
  • 基於閉包實現,沒有煩人的this困擾。
  • 為瀏覽器編寫但也支援其它javascript執行時,瀏覽器支援ie9+(需要引入Mappolyfill)。
  • 與框架無關,可以與任何框架搭配使用。
Mitt 只是提供了幾個簡單的方法,如on,off, emit 等基礎的幾個函數。
在JS中我們使用的話,不需要型別化事件的型別,如下程式碼所示。
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;
  }
};