惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
D
Docker
J
Java Code Geeks
L
LangChain Blog
Microsoft Security Blog
Microsoft Security Blog
The GitHub Blog
The GitHub Blog
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
月光博客
月光博客
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
腾讯CDC
罗磊的独立博客
U
Unit 42
爱范儿
爱范儿
Vercel News
Vercel News
MyScale Blog
MyScale Blog

吐槽大王部落格

在 X (Twitter) 上找 IT 工作靠谱吗? 如何在上游分支被反复重写时优雅地 Rebase 我的 2025 我在经营自己的社交帐号,但可能「玩脱了」 我依然没有学会怎么在网上提出技术问题 我是怎么差点被恶意 npm 包攻击的 我对 Vue Router「动态路由」的一点暴论 不用 eas-cli 编译 React Native (Expo) 应用的 Android 版本 在 IE11 使用 CSS Grid 实现多列卡片列表布局 自建云游戏服务的尝试(2024 更新) 在 Termux 编译和使用 bwm-ng(需要 root) 如何在已有的 Vue CLI 项目使用 esbuild 我的 2021 npm、镜像源与 package-lock.json 新的桌面工作站:HP Z2 G5 Tower 我的 2020 在 Intel 版 MacBook Pro 以 EFI 的形式安装 Windows 10 为什么大家都在安利这一样东西? 新的家庭服务器:MicroServer Gen10 我的 2019 在树莓派(Raspbian)上安装最新稳定版 nginx 驾照 宿舍用小型 UPS 电源与新台灯 拆解我爸 2010 年装的廉价 PC 我的 2018 2018 厦门面基记 我(曾经)设想过的外置智能大脑 没用的无懈可击 tcdw 与 ECMAScript 评论框
【更正版】简单实现类型安全的、能触发 CustomEvent 的 Event...
tcdw · 2024-03-31 · via 吐槽大王部落格

我想写一个 TypeScript 类,这个类提供一系列的事件可供监听。为了实现类型安全,改进开发体验,我自己研究了一下,实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。

经过 JackWorks 的指正,代码改成了这样。

typed-event-target.d.ts 文件内容:

export class TypedEventTarget<T> extends EventTarget {
  // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说(
  addEventListener<K extends keyof T>(
    type: K,
    listener: (this: TypedEventTarget<T>, ev: TypedCustomEvent<K, T[K]>) => any,
    options?: boolean | AddEventListenerOptions,
  ): void;
  addEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject,
    options?: boolean | AddEventListenerOptions,
  ): void;
  removeEventListener<K extends keyof T>(
    type: K,
    listener: (this: TypedEventTarget<T>, ev: TypedCustomEvent<K, T[K]>) => any,
    options?: boolean | EventListenerOptions,
  ): void;
  removeEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject,
    options?: boolean | EventListenerOptions,
  ): void;
  dispatchEvent<K extends keyof T>(event: TypedCustomEvent<K, T[K]>): void;
}

export class TypedCustomEvent<S, T> extends CustomEvent<T> {
  constructor(type: S, eventInitDict?: CustomEventInit<T> | undefined);
}

typed-event-target.js 文件内容:

// 这里我们小小的欺骗了一下 tsc。
// 在运行时下,这个 EventTarget 其实就是原来的 EventTarget,
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export const TypedEventTarget = EventTarget;
export const TypedCustomEvent = CustomEvent;

将这两个文件同时放在一个合适的目录下,就搞定了!

使用方法

我们想要写一个 Person 类,这个类有 nameChangeageChange 这两个自定义事件。那么,我们可以这么写:

import { TypedEventTarget, TypedCustomEvent } from "@/utils/typed-event-target"; // 请根据项目实际情况修改路径

// 创建 Person 类的事件表
export interface PersonEventMap {
  nameChange: string;
  ageChange: number;
}

export default class Person extends TypedEventTarget<PersonEventMap> {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    super();
    this.name = name;
    this.age = age;
  }

  setName(name: string) {
    this.name = name;
    // 在使用 dispatchEvent 时,如果类型不正确,会出现错误
    this.dispatchEvent<"nameChange">(
      new CustomEvent("nameChange", {
        detail: name,
      }),
    );
  }

  setAge(age: number) {
    this.age = age;
    this.dispatchEvent<"ageChange">(
      new CustomEvent("ageChange", {
        detail: age,
      }),
    );
  }
}

调用这个类时,我们绑定事件也会有正确的补全提示和类型检查。

本博文的上一个版本

我想写一个 TypeScript 类,这个类提供一系列的事件可供监听。为了实现类型安全,改进开发体验,我自己研究了一下,实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。

废话不多说,直接上代码。

typed-event-target.d.ts 文件内容:

export default class TypedEventTarget<T> extends EventTarget {
    // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说(
    addEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) => any,
        options?: boolean | AddEventListenerOptions,
    ): void;
    addEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | AddEventListenerOptions,
    ): void;
    removeEventListener<K extends keyof T>(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) => any,
        options?: boolean | EventListenerOptions,
    ): void;
    removeEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | EventListenerOptions,
    ): void;
    dispatchEvent<K extends keyof T>(event: T[K]): void;
}

typed-event-target.js 文件内容:

// 这里我们小小的欺骗了一下 tsc。
// 在运行时下,这个 EventTarget 其实就是原来的 EventTarget,
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export default EventTarget;

将这两个文件同时放在一个合适的目录下,就搞定了!

使用方法

我们想要写一个 Person 类,这个类有 nameChangeageChange 这两个自定义事件。那么,我们可以这么写:

import TypedEventTarget from "@/utils/typed-event-target"; // 请根据项目实际情况修改路径

// 创建 Person 类的事件表 export interface PersonEventMap { nameChange: CustomEvent<string>; ageChange: CustomEvent<number>; }

export default class Person extends TypedEventTarget<PersonEventMap> { name: string; age: number;

constructor(name: string, age: number) {
    super();
    this.name = name;
    this.age = age;
}

setName(name: string) {
    this.name = name;
    // 在使用 dispatchEvent 时,如果类型不正确,会出现错误
    this.dispatchEvent&lt;"nameChange"&gt;(
        new CustomEvent("nameChange", {
            detail: name,
        }),
    );
}

setAge(age: number) {
    this.age = age;
    this.dispatchEvent&lt;"ageChange"&gt;(
        new CustomEvent("ageChange", {
            detail: age,
        }),
    );
}

}

调用这个类时,我们绑定事件也会有正确的补全提示和类型检查:

类型提示
在调用 addEventListener 事件时,会提供准确的名称提示。
类型提示
调用事件的详情内容时,推导出来的类型也是准确的。