中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

TypeScript如何實現類型安全的EventEmitter

發布時間:2023-03-06 17:40:23 來源:億速云 閱讀:127 作者:iii 欄目:開發技術

這篇文章主要介紹了TypeScript如何實現類型安全的EventEmitter的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇TypeScript如何實現類型安全的EventEmitter文章都會有所收獲,下面我們一起來看看吧。

Nodejs 的 EventEmitter 是一個發布訂閱模塊。

利用該類,我們可以實現事件的監聽,被監聽對象會在合適的時機觸發事件,調用監聽對象提供的方法,是模塊間解耦的常用實現。

配合越來越流行的 TypeScript,我們可以通過安裝 @types/node,我們能夠進一步獲得類型能力,減少低級錯誤的出現。但 EventEmitter 的類型實現并不出色,稱不上是類型安全。

通常來說,不同事件對應的響應函數類型是不同的,但 @types/nodeEventEmiiter 類型沒有提供高級類型,而是給一個異常寬松的值

class EventEmitter {
  constructor(options?: EventEmitterOptions);
  // 類型過于寬泛
  on(eventName: string | symbol, listener: (...args: any[]) => void): this;
  emit(eventName: string | symbol, ...args: any[]): boolean;
  // ...其他
}

可以看到,on 方法傳入的事件名類型是 string | symbol,listener 則是隨意任何類型的一個函數即可。emit 傳入的參數也是 any[]

因為過于寬松的類型,如果事件名拼錯了,TypeScript 并不會報錯,當一個 eventEmitter 的事件類型變得非常多,我們就和裸寫 JavaScript 沒什么區別了。

自己動手,豐衣足食,我們不妨 自己實現一個類型安全的 EventEmitter

EventEmitter 實現

因為我其實是在前端用的 EventEmitter,所以寫了一個 EventEmitter 簡易 JavaScript 實現。

class EventEmitter {
  eventMap = {};

  // 添加對應事件的監聽函數
  on(eventName, listener) {
    if (!this.eventMap[eventName]) {
      this.eventMap[eventName] = [];
    }
    this.eventMap[eventName].push(listener);
    return this;
  }

  // 觸發事件
  emit(eventName, ...args) {
    const listeners = this.eventMap[eventName];
    if (!listeners || listeners.length === 0) return false;
    listeners.forEach((listener) => {
      listener(...args);
    });
    return true;
  }

  // 取消對應事件的監聽
  off(eventName, listener) {
    const listeners = this.eventMap[eventName];
    if (listeners && listeners.length > 0) {
      const index = listeners.indexOf(listener);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
    return this;
  }
}

如果你是 nodejs,繼承 EventEmitter 然后改它的類型或許是更好的做法,或者可以 “基于組合而不是繼承” 的方式實現一個。

類型安全的 EventEmitter

接著是將上面的代碼改為 TypeScript。

我們希望的效果是:

const ee = new EventEmitter<{
  update(newVal: string, prevVal: string): void;
  destroy(): void;
}>();


const handler = (newVal: string, prevVal: string) => {
  console.log(newVal, prevVal)
}
ee.on("update", handler);
ee.emit('update', '前端西瓜哥上班前的精神狀態', '前端西瓜哥上班后的精神狀態')
ee.off("update", handler);

// 以下報錯
// 'number' is not assignable to parameter of type 'string'
ee.emit('update', 1, 2)
// (val: number) => void' is not assignable to parameter of type '() => void
ee.on('destroy', (val: number) => {})

EventEmitter 支持接受一個對象結構的 interface 作為類型參數,指定不同的 key 對應的函數類型。

然后我們再調用 on、emit、off 時,如果事件名、函數參數不匹配,編譯就不能通過

代碼實現:

class EventEmitter<T extends Record<string | symbol, any>> {
  private eventMap: Record<keyof T, Array<(...args: any[]) => void>> =
    {} as any;

  // 添加對應事件的監聽函數
  on<K extends keyof T>(eventName: K, listener: T[K]) {
    if (!this.eventMap[eventName]) {
      this.eventMap[eventName] = [];
    }
    this.eventMap[eventName].push(listener);
    return this;
  }

  // 觸發事件
  emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>) {
    const listeners = this.eventMap[eventName];
    if (!listeners || listeners.length === 0) return false;
    listeners.forEach((listener) => {
      listener(...args);
    });
    return true;
  }

  // 取消對應事件的監聽
  off<K extends keyof T>(eventName: K, listener: T[K]) {
    const listeners = this.eventMap[eventName];
    if (listeners && listeners.length > 0) {
      const index = listeners.indexOf(listener);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
    return this;
  }
}

讀者朋友可自行拷貝上面兩段代碼到 TypeScript Playground 測試一下。

簡單講解一下。

首先是開頭的類型參數。

class EventEmitter<T extends Record<string | symbol, any>> {
  //
}

這里的 extends 作用是限定類型范圍,防止提供一個不符合規則的類型參數。

Record 是 TypeScript 自帶的高級類型,根據傳入的 key 和 value 創建一個對象結構(后面說到的 T 就是它)。

Record<string | symbol, any>
// 等價于
{
  [key: string | symbol]: any
}

value 本來的類型應該是 (...args: any[]) => void,好限制為函數。但在不是非字面量類型直傳的情況下無法通過類型檢測,只好改成 any 了。(坑爹的 Index signature for type 'string' is missing 報錯)

然后是 eventMap,它的實際內容是這樣的:

eventMap = {
  event1: [ handler1, handler2 ],
  event2: [ handler3, handler4 ]
}

所以 key 需要為傳入對象類型參數的 key。

函數則不用指定特定類型,因為它是私有的,無法被類外部訪問,沒有做過多的類型推斷,就寬松一些,設置為任何函數類型。

private eventMap: Record<keyof T, Array<(...args: any[]) => void>> =
  {} as any;

這里我用了對象字面量,讀者朋友也可以考慮用 Map 數據結構。

然后是 on 方法,首先 eventName 必須為 T 的 key 的其中之一,因為要推斷 K 這么個內部類型變量,所以我們要在 on 后面加上 <K extends keyof T>,listener 就是對應的 T[K]

on<K extends keyof T>(eventName: K, listener: T[K]): this

off 方法同理,不展開講。

然后是 emit,第一個 eventName 用 keyof T 沒問題,后面需要取出 handler 的參數,作為剩余參數。

emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>): boolean

這里用了 TS 自帶的 Parameters 高級類型,作用是取出函數的參數返回一個數組類型。

臨時擴展自定義事件

如果要給一個已經固定了類型的實例,臨時加一個事件,可以用 & 交叉類型擴展一下。

interface Events {
  update(newVal: string, prevVal: string): void;
  destroy(): void;
}
const ee = new EventEmitter<Events>();

// 用 & 擴展
const ee2 = ee as EventEmitter<
  Events & {
    customA(a: boolean): void;
  }
>;
// 不報錯
ee2.emit('customA', true)

// 或者
(ee as EventEmitter<
  Events & {
    customA(a: boolean): void;
  }
>).emit('customA', true)

關于“TypeScript如何實現類型安全的EventEmitter”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“TypeScript如何實現類型安全的EventEmitter”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

格尔木市| 晋江市| 体育| 兰坪| 襄垣县| 蒙城县| 龙泉市| 桂平市| 望谟县| 龙州县| 西林县| 仙游县| 玉山县| 讷河市| 平乡县| 乡城县| 隆回县| 车险| 同德县| 大竹县| 武城县| 鲜城| 屏山县| 凌源市| 鲁甸县| 文化| 绥化市| 临颍县| 旬阳县| 新竹市| 金乡县| 历史| 正镶白旗| 阿坝县| 隆德县| 锡林浩特市| 新乐市| 三河市| 陵水| 五家渠市| 东丽区|