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

TypeScript如何实现类型安全的EventEmitter

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

TypeScript如何实现类型安全的EventEmitter

这篇文章主要介绍了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 '() => voidee.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”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

免责声明:

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

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

TypeScript如何实现类型安全的EventEmitter

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

下载Word文档

猜你喜欢

TypeScript如何实现类型安全的EventEmitter

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

TypeScript实现类型安全的EventEmitter

这篇文章主要为大家介绍了TypeScript实现类型安全的EventEmitter示例详解有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-06

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

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

Vue如何结合TypeScript进行类型安全开发?

Vue.js结合TypeScript可实现类型安全开发,提升代码质量、可维护性和可重用性。TS的类型检查器确保类型兼容性,防止类型错误。代码重构工具提供高效重构建议。类型注解文档化代码,增强可读性。接口和泛型支持创建可重用组件和抽象类。例如,使用@State()修饰符为数据指定类型,使用@Prop()修饰符为属性指定类型,并使用@Getter()修饰符为计算属性指定类型。结合TypeScript带来的优势包括:提高代码质量、可维护性、可重用性,最终提升开发体验。
Vue如何结合TypeScript进行类型安全开发?
2024-04-02

Typescript类型检查原理之Override如何实现

这篇文章主要介绍了Typescript类型检查原理之Override如何实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Typescript类型检查原理之Override如何实现文章都会有所收获,下面我们一起
2023-06-05

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

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

TypeScript类型实现加减乘除的方法是什么

这篇文章主要介绍了TypeScript类型实现加减乘除的方法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇TypeScript类型实现加减乘除的方法是什么文章都会有所收获,下面我们一起来看看吧。引言在网上
2023-07-06

C#如何实现泛型类

这篇文章主要为大家展示了“C#如何实现泛型类”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C#如何实现泛型类”这篇文章吧。使用泛型集合有些人问我"面向对象编程(OOP)的承诺在哪里?",我的回答
2023-06-17

javascript如何实现类型转换

这篇文章给大家分享的是有关javascript如何实现类型转换的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。方法:1、使用“+”运算符自动进行转换。2、使用JS内置的函数进行转换,例toString()和Stri
2023-06-14

php如何实现枚举类型

这篇文章主要介绍“php如何实现枚举类型”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“php如何实现枚举类型”文章能帮助大家解决问题。枚举类型优点枚举类型在编程中有很多优点,如下:可读性高:枚举类型
2023-07-05

Java的整型类型中如何实现2+2=5

Java的整型类型中如何实现2+2=5,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。先看下这段神奇的Java代码:public static void main(String[
2023-06-02

Ajax原生如何实现MIME类型

小编给大家分享一下Ajax原生如何实现MIME类型,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!问题描述下面的例子是一个Ajax的post请求的代码,这段代码在测
2023-06-08

php image类型如何实现转换

本篇内容主要讲解“php image类型如何实现转换”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php image类型如何实现转换”吧!php image类型实现转换的方法:1、创建一个PHP
2023-06-22

java如何实现类型转换与强制类型转换

这篇文章主要介绍了java如何实现类型转换与强制类型转换,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。java类型转换与强制类型转换如果你以前有编程经验,那么你已经知道把一种
2023-06-03

编程热搜

  • 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动态编译

目录