0Pricing
TypeScript Academy · レッスン

ジェネリックイベントエミッタークラス

再利用可能で型付けされたイベントエミッターをゼロから構築します。

「ジェネリックイベントエミッタークラス」は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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. イベントマップの型付け
  2. 型安全なemitとon
  3. ジェネリックイベントエミッタークラス
  4. リスナー引数の推論
← TypeScript Academyに戻る