概要
JavaScriptにおいて、ユーザーアクションや非同期処理の完了通知には イベント や コールバック が使われる。
どちらも「通知」の手段だが、設計の観点から見ると結合度・柔軟性・スケーラビリティに大きな違いがある。
本稿では、コールバックとカスタムイベントの使い分け、イベントドリブン設計の指針、疎結合化のためのイベントアーキテクチャ戦略を解説する。
1. コールバックの基本と責務
function fetchData(callback) {
setTimeout(() => {
const result = { message: 'done' };
callback(result);
}, 1000);
}
fetchData((res) => {
console.log('Result:', res.message);
});
- ✅ 呼び出し元が「何をすべきか」を直接渡す
- ✅ 高凝集・明示的な1対1の通信に向いている
- ❌ コールバック地獄、拡張性の乏しさ、汎用化困難
2. カスタムイベントの基本と拡張性
const event = new CustomEvent('user:created', {
detail: { name: 'Taro' }
});
document.dispatchEvent(event);
document.addEventListener('user:created', (e) => {
console.log('Created user:', e.detail.name);
});
- ✅ イベント発火元とリスナーが疎結合 → 柔軟な通知設計
- ✅ イベント名・detailにより拡張可能
- ✅ 1対多通信や将来的な変更に強い
3. 使い分け戦略:明示 vs 汎用
条件 | 推奨形式 |
---|---|
単一箇所でのみ使用される通知 | コールバック |
汎用イベント(ログイン・エラー通知など) | カスタムイベント |
外部ライブラリ連携 | コールバック |
UI構成間通信(モジュール間通信) | カスタムイベント |
4. カスタムイベントの拡張設計
class EventBus extends EventTarget {
emit(name, detail) {
this.dispatchEvent(new CustomEvent(name, { detail }));
}
on(name, handler) {
this.addEventListener(name, handler);
}
off(name, handler) {
this.removeEventListener(name, handler);
}
}
const bus = new EventBus();
bus.on('login', (e) => {
console.log('User login:', e.detail.username);
});
bus.emit('login', { username: 'admin' });
- ✅ 独自の イベントバス(PubSub) 実装で再利用性とテスト性を両立
- ✅ アプリケーション全体に 分離された通知ライン を持てる
5. 結合度と拡張性の設計基準
観点 | コールバック | カスタムイベント |
---|---|---|
結合度 | 高(呼び出し側と処理が密結合) | 低(発火元と受信先が分離) |
拡張性 | 低(1対1構造) | 高(1対多、将来の追加に強い) |
テスト性 | 高(関数単位) | 中(リスナ登録のテストが必要) |
保守性 | 低(処理の移植が難しい) | 高(通知が宣言的) |
設計判断フロー
① 通知の対象は一箇所か? → コールバックでシンプルに
② 複数のリスナーに通知したいか? → カスタムイベント
③ モジュール間を疎結合にしたいか? → イベントバスを導入
④ イベントの種類は将来的に増えるか? → カスタムイベントを前提に設計
⑤ イベントにペイロードを持たせたいか? → detail を活用して設計する
よくあるミスと対策
❌ コールバックを多重にネストし、制御不能なコールバック地獄に
→ ✅ Promise / async に置き換える or 責務ごとに分離
❌ カスタムイベントを乱用し、どこで何が発火しているかわからない
→ ✅ EventBusを設け、名前・スコープ・責務を明示
❌ リスナーを登録したまま削除忘れでメモリリーク
→ ✅ off
を徹底 or once
フラグで自動解除する仕組みを導入
結語
イベント設計は、単なる「通知のしかた」ではない。
それは**“情報伝達の責任と結合度を設計し、モジュール間の流れと柔軟性を統御するための戦略”**である。
- コールバック:シンプル・直接的。責務が明確
- カスタムイベント:柔軟・宣言的。責務を分離し、拡張に強い
- 設計段階で「今後の変化」にどこまで耐えるかを見据えて選択する
JavaScriptにおけるイベント設計とは、
“モジュール間の通信を設計構造として表現し、責務分離と拡張性を最大化する技術戦略である。”