Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】JavaScript Event Bus 🚍 (JS + TypeScript)

2025年09月21日に「Dev.to」が公開したITニュース「JavaScript Event Bus 🚍 (JS + TypeScript)」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

JavaScriptのEvent Busは、モジュール間で直接参照せずに情報をやり取りする「発行/購読」の仕組みだ。これにより、コードの疎結合性が高まり、機能間の連携がスムーズになる。認証や通知など、一時的なシグナル伝達に有効だが、大規模な共有状態管理にはReduxなどの利用を推奨する。

出典: JavaScript Event Bus 🚍 (JS + TypeScript) | Dev.to公開日:

ITニュース解説

ソフトウェア開発において、さまざまな機能を持つ部品(モジュール)が連携し合うことは日常的だが、その連携方法には工夫が必要だ。特に、モジュール同士が直接つながりすぎると、柔軟性が失われたり、変更が難しくなったりする問題が生じる。この問題を解決する仕組みの一つに「イベントバス」がある。

イベントバスは、モジュール間の通信を仲介するシンプルな「発表/購読(Publish/Subscribe)」ハブだ。モジュールは、自分が発する「イベント」をバスに「発表」し、他のモジュールは、特定のイベントに「購読」して、そのイベントが発生したときに決められた処理を実行する。これにより、イベントを発信する側(発表者)は、誰がそのイベントを受け取るかを知る必要がなくなり、イベントを受け取る側(購読者)も、誰がイベントを発信したかを知る必要がなくなる。これが「疎結合」と呼ばれる状態で、モジュール同士が独立性を保ち、柔軟に変更や再利用が可能になるという大きなメリットをもたらす。

具体的にイベントバスが役立つのは、以下のような場面である。例えば、ユーザーがログインしたという「認証信号」を複数の異なる機能に伝えたい場合、ログインモジュールは直接それぞれの機能に「ログインしました」と伝えるのではなく、イベントバスに「ユーザーログイン」イベントを発表する。すると、トースト表示機能、分析データ収集機能、ユーザー情報表示機能など、そのイベントに購読している全ての機能がそれぞれ独立して反応できる。このように、一つの信号で多くの独立した反応を引き起こせるのがイベントバスの強みだ。ただし、アプリケーションの主要な状態管理(例えば、ユーザーの現在のカートの中身など、常に最新の状態を共有する必要があるデータ)には、ReduxやZustandといった専用の状態管理ライブラリを使う方が適している。イベントバスは、主に一方向の「信号」や「通知」の伝達に利用するのが良い。

JavaScriptでイベントバスを実装する際、まずイベントを管理するためのクラスを作成する。このクラスは、イベント名と、そのイベントを購読している関数(コールバック)のリストを保持する。EventBusクラスでは、constructorでイベントマップを初期化し、map = Object.create(null)とすることで、JavaScriptのオブジェクトが持つプロトタイプチェーン上の余計なプロパティが混入しない、純粋なマップを作成している。

イベントの購読にはonメソッドを使用する。on(event, cb)は、指定されたeventが発生したときにcbという関数を実行するように登録する。同じ関数が複数回登録されるのを防ぐため、内部ではSetというデータ構造を使っている。Setは重複する要素を自動的に排除してくれるため、効率的な管理が可能だ。onメソッドは、購読を解除するためのヘルパー関数を返すので、後から不要になった購読を簡単に停止できる。

onceメソッドは、onと似ているが、一度イベントが発生して購読関数が実行されたら、自動的にその購読を解除するという特徴がある。これにより、「この通知は一度だけ表示したい」といったシナリオで便利に使える。

購読の解除にはoffメソッドを使う。off(event, cb)は、指定されたイベントから特定のコールバック関数を削除する。もしそのイベントに購読している関数が一つもなくなれば、イベント名自体もマップから削除して、不要な情報を保持しないようにしている。

イベントの発表にはemitメソッドを使う。emit(event, payload)は、指定されたeventを発生させ、そのイベントに購読している全ての関数にpayloadというデータ(イベントの詳細情報)を渡して実行する。この実装では、さらに「ワイルドカード」購読という便利な機能も提供している。例えば、「user:login」というイベントが発表された際に、「user:*」というワイルドカードイベントに購読している関数も実行される仕組みだ。これにより、「ユーザー関連のイベント全てに対して何か処理を行いたい」といった場合に、個別のイベントごとに購読する必要がなくなり、柔軟な対応が可能になる。

TypeScriptを導入することで、イベントバスの利用はさらに安全で便利になる。TypedBusというクラスでは、あらかじめEventsという型定義で、どのようなイベント名が存在し、それぞれのイベントがどのような形のデータ(ペイロード)を持つべきかを厳密に定義する。これにより、イベント名を間違えたり、期待と異なるデータを送信したりするミスが、コードを書いている段階やコンパイル時に検出できるようになる。開発者は、イベント名やペイロードの構造についてオートコンプリートの恩恵を受けられるため、開発効率も向上する。これは「型安全性」と呼ばれ、大規模なアプリケーション開発において非常に重要な要素となる。

WebアプリケーションフレームワークのReactでイベントバスを利用する場合、いくつかのパターンがある。シンプルにアプリケーション全体で共有するグローバルなイベントバスインスタンスを作成する方法は、小規模なアプリで手軽に利用できる。より大規模なアプリや、特定の機能内でイベントバスを独立させたい場合は、ReactのContext APIと組み合わせて、機能ごとにスコープされた(限定された範囲で利用できる)イベントバスを提供するパターンが有効だ。これにより、テストがしやすくなったり、異なる機能が同じイベント名を使ってしまう衝突を防いだりできる。また、Reactのコンポーネントがマウントされたときに購読を開始し、アンマウントされるときに購読を解除するuseEffectフックを適切に使うことで、メモリリーク(不要になった購読が残り続けてメモリを消費すること)を防ぎ、アプリケーションの安定性を保つことができる。

Node.js環境では、EventEmitterという標準モジュールが既に提供されており、これを使うことでイベントバスと同様の機能を手軽に実現できる。サーバーサイドの開発でイベント駆動の処理を実装する際には、このEventEmitterの利用を検討すると良い。一方、ブラウザ環境や、Node.jsとブラウザの両方で利用できる共有ライブラリなどでは、ここで解説したような軽量なカスタムイベントバスが適している場合が多い。

イベントバスを効果的に利用するためのヒントと注意点がある。イベント名には「user:login」「cart:itemAdded」のように、名前空間(カテゴリと具体的なアクション)を持たせることで、イベントの種類を分かりやすくし、衝突を防げる。どのようなイベントがあり、それぞれどのようなデータを持つのかをドキュメント化しておくと、チーム開発での認識合わせに役立つ。また、すべての通信をイベントバスで行うのではなく、前述のように、アプリケーションの主要な状態管理には専用の状態管理ライブラリを使うべきである。イベントバスは、モジュール間の「信号」伝達に限定して活用するのがベストプラクティスだ。メモリリークを防ぐため、購読したイベントは必ず不要になった時点で解除する習慣をつけることが大切である。

まとめると、イベントバスはモジュール間の「疎結合な信号伝達」を実現するための強力なツールである。一度きりの通知や、広範囲に通知したいイベント、ワイルドカードで複数のイベントをまとめて扱いたい場合に特に有効だ。複雑な共有状態を管理する場面では状態管理ライブラリを使い、イベントバスはシンプルな通知や連携のハブとして活用することで、柔軟で保守性の高いシステム開発が可能になる。

関連コンテンツ

関連IT用語