【更正版】简单实现类型安全的、能触发 CustomEvent 的 EventTarget
我想写一个 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
类,这个类有 nameChange
和 ageChange
这两个自定义事件。那么,我们可以这么写:
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
类,这个类有 nameChange
和 ageChange
这两个自定义事件。那么,我们可以这么写:
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<"nameChange">(
new CustomEvent("nameChange", {
detail: name,
}),
);
}
setAge(age: number) {
this.age = age;
this.dispatchEvent<"ageChange">(
new CustomEvent("ageChange", {
detail: age,
}),
);
}
}
调用这个类时,我们绑定事件也会有正确的补全提示和类型检查: