最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用TypeScript實現(xiàn)一個類型安全的EventBus示例詳解

 更新時間:2022年06月29日 11:36:46   作者:羽墨  
EventBus是工作中常用的工具,本文用Typescript實現(xiàn)一個具備基礎(chǔ)功能且類型安全的EventBus,是我近期學習Typescript的知識總結(jié),對TypeScript實現(xiàn)EventBus相關(guān)知識感興趣的朋友一起看看吧

前言

隨著vue3的發(fā)布,TypeScript在國內(nèi)越來越流行,學習TypeScript也隨即變成了大勢所趨。本文就通過實現(xiàn)一個類型安全的EventBus來練習TypeScript,希望對小伙伴們有所幫助。

準備工作

生成一個TypeScript的基礎(chǔ)架子:

// 創(chuàng)建目錄
mkdir ts-event-bus && cd ts-event-bus
// 初始化工程
yarn init -y
// 安裝typescript
yarn add typescript -D
// 生成typescript配置文件
npx tsc --init

這樣一來我們就搭建好了一個TypeScript的基礎(chǔ)架子,為了方便我們后續(xù)的測試,我們需要下載ts-node,它可以讓我們在不編譯TypeScript代碼的情況下運行TypeScript。

yarn add ts-node -D

目標

  • 基礎(chǔ)功能完備,包括注冊,發(fā)布,取消訂閱三個核心功能。
  • 類型安全,能約束我們輸入的參數(shù),并且有代碼提示。

思路

每一個Event都可以注冊多個處理函數(shù),我們用一個Set來保存這些處理函數(shù),再用一個Map來保存Event到對應(yīng)Set的映射,如圖所示:

具體實現(xiàn)

// 定義泛型函數(shù)類型
type Handler<T = any> = (val: T) => void;
class EventBus<Events extends Record<string, any>> {
  /** 保存 key => set 映射 */
  private map: Map<string, Set<Handler>> = new Map();
  on<EventName extends keyof Events>(
    name: EventName,
    handler: Handler<Events[EventName]>
  ) {
    let set: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!set) {
      set = new Set();
      this.map.set(name as string, set);
    }
    set.add(handler);
  }
}

這里我們分成邏輯和類型兩方面來講

邏輯方面,我們初始化了一個空的Map,然后當調(diào)用on 用來注冊事件的時候,先去根據(jù)EventName來找有沒有對應(yīng)的Set,沒有就創(chuàng)建一個,并且把事件添加到Set中,這一部分的代碼相當簡單,實現(xiàn)起來也沒什么難度。

類型方面,我們將EventBus 定義為一個泛型類,并約束泛型為 Events extends Record<string, any>,這樣就約束了傳入的泛型參數(shù)必須是一個對象類型,例如:

type Events = {
    foo : number;
    bar : string;
}

我們可以通過這個類型來獲取key對應(yīng)value的類型

// number;
type ValueTypeOfFoo = Events['foo']

進而可以獲取foo事件對應(yīng)的handler函數(shù)的類型,即:

// (val:number) => void;
type HandlerOfFoo = Handler<Events['foo']>

我們又將on方法設(shè)置為泛型函數(shù),同時約束EventName extends keyof Events,這樣一來Events[EventName] 就是對應(yīng)值的類型,Handler<Events[EventName]>就是處理函數(shù)的類型。通過這樣的方式我們實現(xiàn)了一個類型安全的on方法。

接著我們編寫一段代碼測試一下

可以看到,我們在vscode中編寫代碼的時候,編輯器能給我們代碼提示了。

我們鍵入handler函數(shù),編輯器也會提醒我們val是一個string類型。

當我們傳的參數(shù)不合法的時候,TypeScript也會給我們警告

接下來我們依葫蘆畫瓢實現(xiàn)emit函數(shù)。

class EventBus<Events extends Record<string, any>> {
 ... others code
  /** 觸發(fā)事件 */
  emit<EventName extends keyof Events>(
    name: EventName,
    value: Events[EventName]
  ) {
    const set: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!set) return;
    const copied = [...set];
    copied.forEach((fn) => fn(value));
  }
}

先找到EventName對應(yīng)的Set,如果有就取出并依次執(zhí)行。這里的邏輯也相當簡單,我們編寫代碼測試一下

const bus = new EventBus<{
  foo: string;
  bar: number;
}>();

bus.on("foo", (val) => {
  console.log(val);
});

// 輸出 hello
bus.emit("foo", "hello");

我們在終端運行npx ts-node ./index.ts,輸出hello,說明我們的程序已經(jīng)生效。

接下來我們實現(xiàn)取消訂閱的功能。

{
  ...
  off<EventName extends keyof Events>(
    name?: EventName,
    handler?: Handler<Events[EventName]>
  ): void {
    // 什么都不傳,則清除所有事件
    if (!name) {
      this.map.clear();
      return;
    }

    // 只傳名字,則清除同名事件
    if (!handler) {
      this.map.delete(name as string);
      return;
    }

    // name 和 handler 都傳了,則清除指定handler
    const handlers: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!handlers) {
      return;
    }
    handlers.delete(handler);
  }
}

取消訂閱我們這樣設(shè)計,它傳入0至2個參數(shù),什么都不傳代表清除所有事件,只傳一個參數(shù)代表清除同名事件,傳兩個參數(shù)代表只清除該事件指定的處理函數(shù),所以它的兩個參數(shù)都是可選的,實現(xiàn)的邏輯也非常簡單,我們這里不多贅述。

我們編寫一段測試代碼看下效果

const bus = new EventBus<{
  foo: string;
  bar: number;
}>();

// 測試傳2個參數(shù)的情況
const handlerFoo1 = (val: string) => {
  console.log("2個參數(shù) handlerFoo1 => ", val);
};
bus.on("foo", handlerFoo1);
bus.emit("foo", "hello");
// 打印 2個參數(shù) handlerFoo1 => hello
bus.off("foo", handlerFoo1);
bus.emit("foo", "hello");
// 什么都沒打印

// 測試傳1個參數(shù)的情況
const handlerFoo2 = (val: string) => {
  console.log("1個參數(shù) handlerFoo2 => ", val);
};
const handlerFoo3 = (val: string) => {
  console.log("1個參數(shù) handlerFoo3 => ", val);
};
bus.on("foo", handlerFoo2);
bus.on("foo", handlerFoo3);

bus.emit("foo", "hello");
// 打印 1個參數(shù) handlerFoo2 => hello
// 打印 1個參數(shù) handlerFoo3 => hello
bus.off("foo");
bus.emit("foo", "hello");
// 什么都沒輸出

// 測試傳0個參數(shù)的情況
const handlerFoo4 = (val: string) => {
  console.log("0個參數(shù) handlerFoo4 => ", val);
};
const handlerBar1 = (val: number) => {
  console.log("0個參數(shù) handlerBar1 => ", val);
};
bus.on("foo", handlerFoo4);
bus.on("bar", handlerBar1);

bus.emit("foo", "hello");
bus.emit("bar", 123);
// 打印 1個參數(shù) handlerFoo4 => hello
// 打印 1個參數(shù) handlerBar1 => 123
bus.off();
bus.emit("foo", "hello");
bus.emit("bar", 123);
// 什么都沒輸出

從測試結(jié)果來看,我們的off方法功能也沒問題,這樣就完成了我們的EventBus。

此外,我們還可以給我們的方法加上注釋,這樣在我們鼠標移到api上方和我們輸入?yún)?shù)的時候,編輯器就會有提示。

  /**
   * 訂閱事件
   * @param name 事件名
   * @param handler 事件處理函數(shù)
   */
  on<EventName extends keyof Events>(
    name: EventName,
    handler: Handler<Events[EventName]>
  ) {
    let set: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!set) {
      set = new Set();
      this.map.set(name as string, set);
    }
    set.add(handler);
  }

可以看到,編輯器給我們提供了很好的提示,極大方便了我們的編碼。

我們還可以用函數(shù)重載來改進我們的off方法,以獲得更友好的提示

{
  /**
   *  清除所有事件
   */
  off(): void;
  /**
   * 清除同名事件
   * @param name 事件名
   */
  off<EventName extends keyof Events>(name: EventName): void;
  /**
   * 清除指定事件
   * @param name 事件名
   * @param handler 事件處理函數(shù)
   */
  off<EventName extends keyof Events>(
    name: EventName,
    handler: Handler<Events[EventName]>
  ): void;
  off<EventName extends keyof Events>(
    name?: EventName,
    handler?: Handler<Events[EventName]>
  ): void {
    // 什么都不傳,則清除所有事件
    if (!name) {
      this.map.clear();
      return;
    }

    // 只傳名字,則清除同名事件
    if (!handler) {
      this.map.delete(name as string);
      return;
    }

    // name 和 handler 都傳了,則清除指定handler
    const handlers: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!handlers) {
      return;
    }
    handlers.delete(handler);
  }
}

改造前的提示:

改造后的提示:

至此,我們就完成了一個功能完備,類型安全的EventBus了。

全部代碼

type Handler<T = any> = (val: T) => void;

class EventBus<Events extends Record<string, any>> {
  private map: Map<string, Set<Handler>> = new Map();

  /**
   * 訂閱事件
   * @param name 事件名
   * @param handler 事件處理函數(shù)
   */
  on<EventName extends keyof Events>(
    name: EventName,
    handler: Handler<Events[EventName]>
  ) {
    let set: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!set) {
      set = new Set();
      this.map.set(name as string, set);
    }
    set.add(handler);
  }

  /**
   * 觸發(fā)事件
   * @param name 事件名
   * @param handler 事件處理函數(shù)
   */
  emit<EventName extends keyof Events>(
    name: EventName,
    value: Events[EventName]
  ) {
    const set: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!set) return;
    const copied = [...set];
    copied.forEach((fn) => fn(value));
  }
  /**
   *  清除所有事件
   */
  off(): void;
  /**
   * 清除同名事件
   * @param name 事件名
   */
  off<EventName extends keyof Events>(name: EventName): void;
  /**
   * 清除指定事件
   * @param name 事件名
   * @param handler 處理函數(shù)
   */
  off<EventName extends keyof Events>(
    name: EventName,
    handler: Handler<Events[EventName]>
  ): void;

  off<EventName extends keyof Events>(
    name?: EventName,
    handler?: Handler<Events[EventName]>
  ): void {
    // 什么都不傳,則清除所有事件
    if (!name) {
      this.map.clear();
      return;
    }

    // 只傳名字,則清除同名事件
    if (!handler) {
      this.map.delete(name as string);
      return;
    }

    // name 和 handler 都傳了,則清除指定handler
    const handlers: Set<Handler<Events[EventName]>> | undefined = this.map.get(
      name as string
    );
    if (!handlers) {
      return;
    }
    handlers.delete(handler);
  }
}

const bus = new EventBus<{
  foo: string;
  bar: number;
}>();

// 測試傳2個參數(shù)的情況
const handlerFoo1 = (val: string) => {
  console.log("2個參數(shù) handlerFoo1 => ", val);
};
bus.on("foo", handlerFoo1);
bus.emit("foo", "hello");
// 打印 2個參數(shù) handlerFoo1 => hello
bus.off("foo", handlerFoo1);
bus.emit("foo", "hello");
// 什么都沒打印

// 測試傳1個參數(shù)的情況
const handlerFoo2 = (val: string) => {
  console.log("1個參數(shù) handlerFoo2 => ", val);
};
const handlerFoo3 = (val: string) => {
  console.log("1個參數(shù) handlerFoo3 => ", val);
};
bus.on("foo", handlerFoo2);
bus.on("foo", handlerFoo3);

bus.emit("foo", "hello");
// 打印 1個參數(shù) handlerFoo2 => hello
// 打印 1個參數(shù) handlerFoo3 => hello
bus.off("foo");
bus.emit("foo", "hello");
// 什么都沒輸出

// 測試傳0個參數(shù)的情況
const handlerFoo4 = (val: string) => {
  console.log("0個參數(shù) handlerFoo4 => ", val);
};
const handlerBar1 = (val: number) => {
  console.log("0個參數(shù) handlerBar1 => ", val);
};
bus.on("foo", handlerFoo4);
bus.on("bar", handlerBar1);

bus.emit("foo", "hello");
bus.emit("bar", 123);
// 打印 1個參數(shù) handlerFoo4 => hello
// 打印 1個參數(shù) handlerBar1 => 123
bus.off();
bus.emit("foo", "hello");
bus.emit("bar", 123);
// 什么都沒輸出

后記

EventBus是工作中常用的工具,本文用Typescript實現(xiàn)一個具備基礎(chǔ)功能且類型安全的EventBus,是我近期學習Typescript的知識總結(jié),希望小伙伴們有所幫助。

本文的代碼已同步到GitHub上,喜歡的同學可以 clone 下來學習,如果喜歡那就點個??吧!

到此這篇關(guān)于用TypeScript實現(xiàn)一個類型安全的EventBus的文章就介紹到這了,更多相關(guān)TypeScript實現(xiàn)類型安全的EventBus內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • js點擊時關(guān)閉該范圍下拉菜單之外的菜單方法

    js點擊時關(guān)閉該范圍下拉菜單之外的菜單方法

    下面小編就為大家分享一篇js點擊時關(guān)閉該范圍下拉菜單之外的菜單方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • JS實現(xiàn)的簡單拖拽功能示例

    JS實現(xiàn)的簡單拖拽功能示例

    這篇文章主要介紹了JS實現(xiàn)的簡單拖拽功能,涉及javascript鼠標事件響應(yīng)及頁面元素屬性動態(tài)操作相關(guān)技巧,需要的朋友可以參考下
    2017-03-03
  • JavaScript實現(xiàn)的多種鼠標拖放效果

    JavaScript實現(xiàn)的多種鼠標拖放效果

    這篇文章主要介紹了JavaScript實現(xiàn)的多種鼠標拖放效果,涉及JavaScript響應(yīng)鼠標事件動態(tài)變換頁面元素屬性的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-11-11
  • javascript原型繼承工作原理和實例詳解

    javascript原型繼承工作原理和實例詳解

    這篇文章主要為大家詳細介紹了javascript原型繼承,闡明什么是原型繼承,以及在JavaScript中究竟如何使用原型繼承,感興趣的小伙伴們可以參考一下
    2016-04-04
  • js定時器出現(xiàn)第一次延遲的原因及解決方法

    js定時器出現(xiàn)第一次延遲的原因及解決方法

    在本篇文章里小編給大家整理的是一篇關(guān)于js定時器出現(xiàn)第一次延遲的原因及解決方法,對此有需要的朋友們可以學習下。
    2021-01-01
  • 關(guān)于JavaScript奇怪又實用的六個姿勢

    關(guān)于JavaScript奇怪又實用的六個姿勢

    這篇文章主要給大家介紹了關(guān)于JavaScript奇怪又實用的六個姿勢,這些技巧和建議是我平常在開發(fā)項目上會用到的,希望能讓大家學到知識,需要的朋友可以參考下
    2021-10-10
  • JavaScript中的console.profile()函數(shù)詳細介紹

    JavaScript中的console.profile()函數(shù)詳細介紹

    這篇文章主要介紹了JavaScript中的console.profile()函數(shù)詳細介紹,本文講解了console.profile()函數(shù)的瀏覽器支持情況、console.profile()的使用、Firebug中Profile按鈕的使用等內(nèi)容,需要的朋友可以參考下
    2014-12-12
  • JavaScript中setInterval的用法總結(jié)

    JavaScript中setInterval的用法總結(jié)

    這篇文章主要是對JavaScript中setInterval的用法進行了詳細的總結(jié)介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-11-11
  • 深入探究JavaScript中WeakMap的原理與用法

    深入探究JavaScript中WeakMap的原理與用法

    這篇文章主要對JavaScript中的WeakMap進行深入探究,包括它的原理、用法和注意事項等,文中的示例代碼簡潔易懂,需要的小伙伴可以參考一下
    2023-05-05
  • 詳解ES6系列之私有變量的實現(xiàn)

    詳解ES6系列之私有變量的實現(xiàn)

    這篇文章主要介紹了詳解ES6系列之私有變量的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11

最新評論

衡阳市| 合山市| 射阳县| 葵青区| 噶尔县| 安多县| 黑龙江省| 青河县| 开封县| 德安县| 新昌县| 双流县| 遂溪县| 昌宁县| 横山县| 元朗区| 孝昌县| 和平区| 资中县| 梅河口市| 巴里| 长治市| 武威市| 景洪市| 施秉县| 宜州市| 望都县| 昌吉市| 雷州市| 宁强县| 启东市| 玉龙| 眉山市| 巩义市| 阿鲁科尔沁旗| 拜城县| 咸阳市| 尼勒克县| 嘉荫县| 洞口县| 芦山县|