我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用TypeScript实现一个类型安全的EventBus

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用TypeScript实现一个类型安全的EventBus

这篇文章主要介绍“如何使用TypeScript实现一个类型安全的EventBus”,在日常操作中,相信很多人在如何使用TypeScript实现一个类型安全的EventBus问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用TypeScript实现一个类型安全的EventBus”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

准备工作

生成一个TypeScript的基础架子:

// 创建目录mkdir ts-event-bus && cd ts-event-bus// 初始化工程yarn init -y// 安装typescriptyarn add typescript -D// 生成typescript配置文件npx tsc --init

这样一来我们就搭建好了一个TypeScript的基础架子,为了方便我们后续的测试,我们需要下载ts-node,它可以让我们在不编译TypeScript代码的情况下运行TypeScript

yarn add ts-node -D

目标

  • 基础功能完备,包括注册,发布,取消订阅三个核心功能。

  • 类型安全,能约束我们输入的参数,并且有代码提示。

思路

每一个Event都可以注册多个处理函数,我们用一个Set来保存这些处理函数,再用一个Map来保存Event到对应Set的映射,如图所示:

如何使用TypeScript实现一个类型安全的EventBus

具体实现

// 定义泛型函数类型type Handler<T = any> = (val: T) => void;class EventBus<Events extends Record<string, any>> {    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,然后当调用on 用来注册事件的时候,先去根据EventName来找有没有对应的Set,没有就创建一个,并且把事件添加到Set中,这一部分的代码相当简单,实现起来也没什么难度。

类型方面,我们将EventBus 定义为一个泛型类,并约束泛型为 Events extends Record<string, any>,这样就约束了传入的泛型参数必须是一个对象类型,例如:

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

我们可以通过这个类型来获取key对应value的类型

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

进而可以获取foo事件对应的handler函数的类型,即:

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

我们又将on方法设置为泛型函数,同时约束EventName extends keyof Events,这样一来Events[EventName] 就是对应值的类型,Handler<Events[EventName]>就是处理函数的类型。通过这样的方式我们实现了一个类型安全的on方法。

接着我们编写一段代码测试一下

可以看到,我们在vscode中编写代码的时候,编辑器能给我们代码提示了。

如何使用TypeScript实现一个类型安全的EventBus

我们键入handler函数,编辑器也会提醒我们val是一个string类型。

如何使用TypeScript实现一个类型安全的EventBus

当我们传的参数不合法的时候,TypeScript也会给我们警告

如何使用TypeScript实现一个类型安全的EventBus

如何使用TypeScript实现一个类型安全的EventBus

接下来我们依葫芦画瓢实现emit函数。

class EventBus<Events extends Record<string, any>> { ... others code    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对应的Set,如果有就取出并依次执行。这里的逻辑也相当简单,我们编写代码测试一下

const bus = new EventBus<{  foo: string;  bar: number;}>();bus.on("foo", (val) => {  console.log(val);});// 输出 hellobus.emit("foo", "hello");

我们在终端运行npx ts-node ./index.ts,输出hello,说明我们的程序已经生效。

接下来我们实现取消订阅的功能。

{  ...  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);  }}

取消订阅我们这样设计,它传入0至2个参数,什么都不传代表清除所有事件,只传一个参数代表清除同名事件,传两个参数代表只清除该事件指定的处理函数,所以它的两个参数都是可选的,实现的逻辑也非常简单,我们这里不多赘述。

我们编写一段测试代码看下效果

const bus = new EventBus<{  foo: string;  bar: number;}>();// 测试传2个参数的情况const handlerFoo1 = (val: string) => {  console.log("2个参数 handlerFoo1 => ", val);};bus.on("foo", handlerFoo1);bus.emit("foo", "hello");// 打印 2个参数 handlerFoo1 => hellobus.off("foo", handlerFoo1);bus.emit("foo", "hello");// 什么都没打印// 测试传1个参数的情况const handlerFoo2 = (val: string) => {  console.log("1个参数 handlerFoo2 => ", val);};const handlerFoo3 = (val: string) => {  console.log("1个参数 handlerFoo3 => ", val);};bus.on("foo", handlerFoo2);bus.on("foo", handlerFoo3);bus.emit("foo", "hello");// 打印 1个参数 handlerFoo2 => hello// 打印 1个参数 handlerFoo3 => hellobus.off("foo");bus.emit("foo", "hello");// 什么都没输出// 测试传0个参数的情况const handlerFoo4 = (val: string) => {  console.log("0个参数 handlerFoo4 => ", val);};const handlerBar1 = (val: number) => {  console.log("0个参数 handlerBar1 => ", val);};bus.on("foo", handlerFoo4);bus.on("bar", handlerBar1);bus.emit("foo", "hello");bus.emit("bar", 123);// 打印 1个参数 handlerFoo4 => hello// 打印 1个参数 handlerBar1 => 123bus.off();bus.emit("foo", "hello");bus.emit("bar", 123);// 什么都没输出

从测试结果来看,我们的off方法功能也没问题,这样就完成了我们的EventBus

此外,我们还可以给我们的方法加上注释,这样在我们鼠标移到api上方和我们输入参数的时候,编辑器就会有提示。

    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);  }

如何使用TypeScript实现一个类型安全的EventBus

如何使用TypeScript实现一个类型安全的EventBus

可以看到,编辑器给我们提供了很好的提示,极大方便了我们的编码。

我们还可以用函数重载来改进我们的off方法,以获得更友好的提示

{    off(): void;    off<EventName extends keyof Events>(name: EventName): void;    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);  }}

改造前的提示:

如何使用TypeScript实现一个类型安全的EventBus

改造后的提示:

如何使用TypeScript实现一个类型安全的EventBus

至此,我们就完成了一个功能完备,类型安全的EventBus了。

全部代码

type Handler<T = any> = (val: T) => void;class EventBus<Events extends Record<string, any>> {  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);  }    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;    off<EventName extends keyof Events>(name: EventName): void;    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个参数的情况const handlerFoo1 = (val: string) => {  console.log("2个参数 handlerFoo1 => ", val);};bus.on("foo", handlerFoo1);bus.emit("foo", "hello");// 打印 2个参数 handlerFoo1 => hellobus.off("foo", handlerFoo1);bus.emit("foo", "hello");// 什么都没打印// 测试传1个参数的情况const handlerFoo2 = (val: string) => {  console.log("1个参数 handlerFoo2 => ", val);};const handlerFoo3 = (val: string) => {  console.log("1个参数 handlerFoo3 => ", val);};bus.on("foo", handlerFoo2);bus.on("foo", handlerFoo3);bus.emit("foo", "hello");// 打印 1个参数 handlerFoo2 => hello// 打印 1个参数 handlerFoo3 => hellobus.off("foo");bus.emit("foo", "hello");// 什么都没输出// 测试传0个参数的情况const handlerFoo4 = (val: string) => {  console.log("0个参数 handlerFoo4 => ", val);};const handlerBar1 = (val: number) => {  console.log("0个参数 handlerBar1 => ", val);};bus.on("foo", handlerFoo4);bus.on("bar", handlerBar1);bus.emit("foo", "hello");bus.emit("bar", 123);// 打印 1个参数 handlerFoo4 => hello// 打印 1个参数 handlerBar1 => 123bus.off();bus.emit("foo", "hello");bus.emit("bar", 123);// 什么都没输出

到此,关于“如何使用TypeScript实现一个类型安全的EventBus”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何使用TypeScript实现一个类型安全的EventBus

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何使用TypeScript实现一个类型安全的EventBus

这篇文章主要介绍“如何使用TypeScript实现一个类型安全的EventBus”,在日常操作中,相信很多人在如何使用TypeScript实现一个类型安全的EventBus问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对
2023-07-02

TypeScript如何实现类型安全的EventEmitter

这篇文章主要介绍了TypeScript如何实现类型安全的EventEmitter的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇TypeScript如何实现类型安全的EventEmitter文章都会有所收获,下面
2023-07-05

如何使用TypeScript实现一个IoC容器

这篇文章主要介绍“如何使用TypeScript实现一个IoC容器”,在日常操作中,相信很多人在如何使用TypeScript实现一个IoC容器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用TypeScr
2023-06-16

使用@ConfigurationProperties实现类型安全的配置过程

这篇文章主要介绍了使用@ConfigurationProperties实现类型安全的配置过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-13

PHP一个类调用另一个类的方法如何实现

本篇内容主要讲解“PHP一个类调用另一个类的方法如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“PHP一个类调用另一个类的方法如何实现”吧!在面向对象编程中,类与类之间的依赖关系很常见。当
2023-07-06

如何使用Python实现一个简易的ORM模型

目录元类描述器本文记录下自己使用Python实现一个简易的ORM模型 使用到的知识 1、元类 2、描述器 元类 对于元类,我的理解其实也便较浅,大概是这个意思 所有的类都是使用元类来进行创建的,而所有的类的父类中必然是object(针对Py
2022-06-02

Vue如何用枚举类型实现一个HTML下拉框

本篇内容主要讲解“Vue如何用枚举类型实现一个HTML下拉框”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue如何用枚举类型实现一个HTML下拉框”吧!第一步: 编写下拉框需要的枚举类型 S
2023-07-04

利用SpringMVC如何实现一个自定义类型转换器

这篇文章将为大家详细讲解有关利用SpringMVC如何实现一个自定义类型转换器,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。我们在使用SpringMVC时,常常需要把表单中的参数映射到我们对
2023-05-31

Python中如何实现一个线程安全的缓存对象

Python中如何实现一个线程安全的缓存对象随着多线程编程在Python中的越来越被广泛应用,线程安全性变得愈发重要。在并发环境中,多个线程同时读写共享资源时,可能会导致数据不一致或者意外的结果。为了解决这个问题,我们可以使用线程安全的缓存
2023-10-22

如何使用HTML和CSS实现一个全屏滚动布局

随着互联网的发展,页面设计越来越注重用户体验。全屏滚动布局就是一种常用的设计方式,它能够让页面更加吸引人,并且提供了流畅的用户导航体验。如果你想学习如何使用HTML和CSS实现一个全屏滚动布局,本文将为你提供具体的代码示例和实现步骤。在开始
2023-10-21

如何使用HTML和CSS实现一个全屏背景布局

在网页设计中,全屏背景布局是一种常见且炫酷的效果,能够更好地展示网站内容,给用户带来良好的视觉体验。本文将介绍如何使用HTML和CSS实现一个全屏背景布局,并提供具体的代码示例。首先,在HTML文件中创建一个基本的布局结构。以下是一个简单的
2023-10-21

如何使用HTML和CSS实现一个全屏视差布局

如何使用HTML和CSS实现一个全屏视差布局全屏视差效果是一种在网页设计中经常使用的技术,它能给用户带来更丰富、更吸引人的视觉体验。本文将介绍如何使用HTML和CSS实现一个简单的全屏视差布局,并提供具体的代码示例。视差效果的原理是通过同时
2023-10-25

如何使用HTML和CSS实现一个全屏遮罩布局

首先,让我们来创建HTML结构。在HTML文件中,我们会使用一个div元素来作为遮罩的容器,并在其中添加内容,如下所示:全屏
2023-10-21

如何设计一个安全的MySQL表结构来实现支付功能?

如何设计一个安全的MySQL表结构来实现支付功能?随着电子商务的快速发展,支付功能成为了网站和应用程序的核心需求之一。设计一个安全的MySQL表结构是确保支付功能的可靠性和安全性的重要步骤。本文将介绍如何设计一个安全的MySQL表结构来实现
如何设计一个安全的MySQL表结构来实现支付功能?
2023-10-31

详解如何使用C++写一个线程安全的单例模式

这篇文章主要为大家详细介绍了如何使用C++写一个线程安全的单例模式,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解一下
2022-11-13

如何设计一个安全的MySQL表结构来实现验证码功能?

如何设计一个安全的MySQL表结构来实现验证码功能?随着互联网的迅速发展,验证码功能已经成为了网站和应用程序中常见的安全验证方式之一。在开发过程中,如何设计一个安全稳定的MySQL表结构来存储和使用验证码数据是一个至关重要的问题。本文将详细
如何设计一个安全的MySQL表结构来实现验证码功能?
2023-10-31

在SpringMVC中使用 Controller 如何实现返回可选类型的值

本篇文章为大家展示了在SpringMVC中使用 Controller 如何实现返回可选类型的值,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。spring mvc 支持如下的返回方式:ModelAnd
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录