🎯
TypeScript で EventTarget を継承する方法
実際のコード
TypeScript において EventTarget
を継承したクラスを作成するコードは以下のように書くことができます。
/**
* @file Pure Typed EventTarget Example
* @license Unlicense
*/
declare class TypedEventTarget<EventMap extends Record<string, Event>>
extends EventTarget {
addEventListener<Type extends keyof EventMap>(
type: Type,
listener: (this: this, evt: EventMap[Type]) => void,
options?: boolean | AddEventListenerOptions
): void;
addEventListener(
...args: Parameters<EventTarget["addEventListener"]>
): void;
removeEventListener<Type extends keyof EventMap>(
type: Type,
listener: (this: this, evt: EventMap[Type]) => void,
options?: boolean | EventListenerOptions
): void;
removeEventListener(
...args: Parameters<EventTarget["removeEventListener"]>
): void;
}
// TypeScript 4.7+
export class Foo extends (EventTarget as typeof TypedEventTarget<{
foo: CustomEvent<string>;
bar: CustomEvent<number>;
baz: CustomEvent<{ ok: boolean }>;
}>) { }
// TypeScript 4.6
export class Foo extends (EventTarget as {
new (): TypedEventTarget<{
foo: CustomEvent<string>;
bar: CustomEvent<number>;
baz: CustomEvent<{ ok: boolean }>;
}>
}) { }
上記 TypeScript コードをコンパイルすると以下の JavaScript コードが得られます。
export class Foo extends EventTarget { }
どうしてこうなった
単に EventTarget
を継承しただけでは addEventListener
の型推論が思うように効かず使い勝手が悪くなってしまいます。
上記 TypedEventTarget
を使用すると以下のように Foo
クラスを使用することができます。
new Foo().addEventListener("baz", e => {
// ^^^^^ "foo"、"bar"、"baz" のオートコンプリートが効く
e.detail;
// ^^^^^^ 型推論が効いて { ok: boolean } 型になる
})
関連項目
-
Strongly typed event emitters using EventTarget in TypeScript (dev.to)
- こちらと似た記事のコードを元に改良し、
declare class
が最もシンプルに実現できることを見つけました。
- こちらと似た記事のコードを元に改良し、
-
typescript-event-target (npmjs.com)
- 外部依存ライブラリが増えるのを気にしなければこちらでもよいと思います。
Discussion