TypeScript中級

中級 TypeScriptで学ぶイベント駆動設計|ケーススタディ編

導入

近年、アプリケーションの設計においてイベント駆動設計が注目を集めています。特に、リアルタイム性が求められるシステムや、非同期処理が多い環境では、その利点が際立ちます。本記事では、架空のプロジェクトを通じて、TypeScriptを用いたイベント駆動設計の具体的な適用例を考察します。

教科書レベルの解説(イベント駆動設計)

重要な概念の整理

イベント駆動設計は、システムのコンポーネントがイベントを発行し、それに対して他のコンポーネントが反応する形で動作します。この設計は、疎結合を促進し、スケーラビリティを向上させる特性があります。イベントは、ユーザーのアクションやシステム内部の状態変化など、様々なトリガーによって発生します。

コード例(TypeScript)


class EventEmitter {
    private listeners: { [event: string]: Function[] } = {};

    public on(event: string, listener: Function) {
        if (!this.listeners[event]) {
            this.listeners[event] = [];
        }
        this.listeners[event].push(listener);
    }

    public emit(event: string, ...args: any[]) {
        if (this.listeners[event]) {
            this.listeners[event].forEach(listener => listener(...args));
        }
    }
}

// 使用例
const eventEmitter = new EventEmitter();

eventEmitter.on('dataReceived', (data: string) => {
    console.log(`データ受信: ${data}`);
});

eventEmitter.emit('dataReceived', 'サンプルデータ');

コードの行ごとの解説

  1. class EventEmitter { – イベントを管理するクラスを定義します。
  2. private listeners: { [event: string]: Function[] } = {}; – イベントとリスナーのマッピングを保持するためのオブジェクトを宣言します。
  3. public on(event: string, listener: Function) { – イベントにリスナーを登録するメソッドです。
  4. if (!this.listeners[event]) { this.listeners[event] = []; } – 新しいイベントの場合、リスナーの配列を初期化します。
  5. this.listeners[event].push(listener); – リスナーを該当のイベントのリストに追加します。
  6. public emit(event: string, ...args: any[]) { – イベントを発火させるメソッドです。
  7. if (this.listeners[event]) { – リスナーが登録されているか確認します。
  8. this.listeners[event].forEach(listener => listener(...args)); – 登録されたリスナーを呼び出します。
  9. const eventEmitter = new EventEmitter(); – イベントエミッターのインスタンスを作成します。
  10. eventEmitter.on('dataReceived', (data: string) => { ... }); – ‘dataReceived’イベントにリスナーを登録します。
  11. eventEmitter.emit('dataReceived', 'サンプルデータ'); – ‘dataReceived’イベントを発火し、データを渡します。

ケーススタディ編

架空のプロジェクトとして、オンラインショッピングサイトのカート機能を考えます。このシステムでは、商品がカートに追加されたときに、カートの合計金額を更新する必要があります。この場合、イベント駆動設計を用いることで、カートの状態管理を効率化できます。

具体的には、商品が追加されるたびに「itemAdded」というイベントを発火し、リスナーがそのイベントを受け取って合計金額を計算します。以下のような実装が考えられます。


class ShoppingCart extends EventEmitter {
    private items: { name: string; price: number }[] = [];

    public addItem(item: { name: string; price: number }) {
        this.items.push(item);
        this.emit('itemAdded', item);
    }

    public onItemAdded(listener: (item: { name: string; price: number }) => void) {
        this.on('itemAdded', listener);
    }

    public getTotal(): number {
        return this.items.reduce((total, item) => total + item.price, 0);
    }
}

// 使用例
const cart = new ShoppingCart();
cart.onItemAdded(item => {
    console.log(`商品追加: ${item.name}, 現在の合計: ${cart.getTotal()}`);
});
cart.addItem({ name: '商品A', price: 100 });
cart.addItem({ name: '商品B', price: 200 });

この実装では、「itemAdded」イベントが発火するたびに、合計金額が更新され、ユーザーにフィードバックが提供されます。しかし、注意すべき点は、リスナーが多くなりすぎると、パフォーマンスに影響を与える可能性があることです。リスナーの管理や、不要になったリスナーの削除を適切に行うことが、システムの健全性を保つ鍵となります。

まとめ

  • イベント駆動設計を用いることで、システムのコンポーネント間の疎結合を実現できる。
  • リスナーの管理が重要であり、不要なリスナーの削除を怠らないことがパフォーマンス向上につながる。
  • TypeScriptの型システムを活用することで、イベントの取り扱いがより安全に行える。