ジェネリックイベントエミッタークラス
再利用可能で型付けされたイベントエミッターをゼロから構築します。
「ジェネリックイベントエミッタークラス」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全4レッスンが含まれています。
目標:型付きTypedEmitterクラス
イベントマップEに対してジェネリックな、再利用可能なTypedEmitter<E>クラスを作成します。on、off、emitの各メソッドも完全に型付けします。
interface Events { login: { userId: string }; logout: void }
// class TypedEmitter<Events> will manage handlers safely.
console.log("building a typed emitter");ハンドラーの型エイリアス
イベント名をパラメーターとするハンドラー型を定義し、各リスナーが正しいペイロードを受け取れるようにします。
type Handler<E, K extends keyof E> = (payload: E[K]) => void;
// E[K] is the payload type for event K in map E.
console.log("handler typed by E[K]");listeners Map
イベント名をキーとしてハンドラーを格納します。mapped typeによって、各キーに正しく型付けされたハンドラーの配列を割り当てます。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
}
console.log("listeners map ready");onを実装する
onメソッドは、そのイベントの配列にハンドラーを追加します。初めて使うときには配列を作成します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(event: K, handler: (p: E[K]) => void): void {
(this.listeners[event] ||= []).push(handler);
}
}
console.log("on implemented");emitを実装する
emitメソッドは、イベントに対応するハンドラーを検索し、型付けされたペイロードを渡してそれぞれを呼び出します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, payload: E[K]): void {
(this.listeners[e] || []).forEach(h => h(payload));
}
}
console.log("emit implemented");offを実装する
offメソッドは、配列から以前に登録したハンドラーをフィルタリングして削除します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
off<K extends keyof E>(e: K, h: (p: E[K]) => void) {
this.listeners[e] = (this.listeners[e] || []).filter(x => x !== h);
}
}
console.log("off implemented");クラスを完成させる
ここでは、指定した任意のイベントマップに対してジェネリックな、3つのメソッドをすべて備えた完全なクラスを示します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
off<K extends keyof E>(e: K, h: (p: E[K]) => void) { this.listeners[e] = (this.listeners[e] || []).filter(x => x !== h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
console.log("TypedEmitter complete");TypedEmitterを使う
具体的なイベントマップを指定してインスタンス化します。これで、onとemitがそのイベントに対して完全に型付けされます。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { login: { userId: string } }
const bus = new TypedEmitter<Events>();
bus.on("login", p => console.log("hi", p.userId));
bus.emit("login", { userId: "u1" });型エラーを検出できる
このクラスは、単独の関数の場合と同じように、不明なイベントや誤ったペイロードをコンパイル時に拒否します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { ping: { at: number } }
const bus = new TypedEmitter<Events>();
// bus.emit("pong", { at: 1 }); // Error
bus.emit("ping", { at: 1 });
console.log("typed bus");イベントごとに複数のハンドラーを登録する
各イベントが配列に対応付けられているため、複数のハンドラーを登録でき、emit時にはすべてが実行されます。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { tick: { n: number } }
const bus = new TypedEmitter<Events>();
bus.on("tick", p => console.log("a", p.n));
bus.on("tick", p => console.log("b", p.n));
bus.emit("tick", { n: 7 });拡張できる基盤
このクラスは確かな基盤となります。次はリスナーの引数推論を改良し、onceのセマンティクスを追加します。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
const bus = new TypedEmitter<{ go: void }>();
bus.on("go", () => console.log("go!"));
bus.emit("go", undefined as void);クイックチェック:ジェネリックなエミッタークラス
ジェネリックなイベントエミッタークラスについて理解度を確認します。
まとめ:ジェネリックなイベントエミッタークラス
mapped listeners storeと、完全に型付けされたon、off、emitメソッドを備えたTypedEmitter<E>を作成しました。これにより、任意のイベントマップで再利用できる、型安全なpub/subを実現できます。
class TypedEmitter<E> {
private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
const bus = new TypedEmitter<{ hi: { name: string } }>();
bus.on("hi", p => console.log(p.name));
bus.emit("hi", { name: "Ada" });よくある質問
「ジェネリックイベントエミッタークラス」レッスンは無料ですか?
はい。「ジェネリックイベントエミッタークラス」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全4レッスンが含まれています。
「ジェネリックイベントエミッタークラス」で何を学びますか?
再利用可能で型付けされたイベントエミッターをゼロから構築します。 ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
TypeScript Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「ジェネリックイベントエミッタークラス」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このTypeScript Academyレッスンでコードを書いて実行できますか?
はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- イベントマップの型付け
- 型安全なemitとon
- ジェネリックイベントエミッタークラス
- リスナー引数の推論