2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptにおけるイベント駆動設計:Pub/Sub・EventEmitter・状態伝播の分離戦略

Posted at

概要

イベント駆動とは、「状態を直接渡さず、変化を通知する」設計原則である。
双方向バインディングや過剰な依存がもたらす“密結合”を回避し、拡張可能でテストしやすい構造を実現するための戦略的手法である。

本稿では、次の観点でイベント駆動設計を体系化する:

  • カスタムイベントと責務分離
  • EventEmitterによる発火/購読構造
  • Pub/Subパターンと抽象イベント管理
  • 状態と副作用の非同期伝播
  • UIロジックの分割と拡張戦略

1. イベント駆動の基本構造

✅ 発火(emit)と購読(on)

const emitter = new EventTarget();

function onSave(e) {
  console.log('保存イベント受信:', e.detail);
}

emitter.addEventListener('save', onSave);
emitter.dispatchEvent(new CustomEvent('save', { detail: { id: 1 } }));

→ ✅ 関係性を直接持たずに状態を伝播


2. カスタムEventEmitterクラスの導入

class Emitter {
  constructor() {
    this.events = {};
  }

  on(type, handler) {
    (this.events[type] ||= []).push(handler);
  }

  emit(type, payload) {
    (this.events[type] || []).forEach(fn => fn(payload));
  }
}

→ ✅ UI・非UI関係なく、モジュール間で共通の通知構造として再利用可能


3. Pub/Subパターンとモジュール間通信

// pubsub.js
export const PubSub = (() => {
  const events = new Map();

  return {
    subscribe: (type, handler) => {
      if (!events.has(type)) events.set(type, []);
      events.get(type).push(handler);
    },
    publish: (type, payload) => {
      (events.get(type) || []).forEach(fn => fn(payload));
    }
  };
})();

→ ✅ 発行者と購読者の完全分離が可能
→ ✅ 特定モジュールへの依存がなくなる


4. 状態とイベントの協調設計

❌ 状態を直接変更し副作用が伝播

user.name = 'Toto';
updateHeader(); // tight coupling

✅ 状態更新後、イベント通知でUI更新

user.name = 'Toto';
PubSub.publish('user/updated', user);

→ ✅ 状態 → 通知 → 各コンポーネントでUI更新
→ ✅ 状態と副作用が明確に分離される


5. コンポーネント間のイベント連携(例:チャット)

// messageInput.js
button.addEventListener('click', () => {
  const text = input.value;
  PubSub.publish('chat/send', { text });
});

// messageList.js
PubSub.subscribe('chat/send', ({ text }) => {
  const li = document.createElement('li');
  li.textContent = text;
  list.appendChild(li);
});

→ ✅ 直接依存せずとも、機能的連携が成立


6. イベント命名規則とスコープ設計

  • user/created
  • user/updated
  • auth/login/success
  • chat/message/received

→ ✅ 階層構造 or ドット記法で命名し、分類・拡張性を担保
→ ✅ ロジックが増えても“バケツリレー”にならない


7. 非同期イベントチェーンと副作用管理

PubSub.subscribe('file/upload', async (file) => {
  const result = await uploadFile(file);
  PubSub.publish('file/upload/success', result);
});

→ ✅ イベントチェーンによって非同期処理も追跡可能
→ ✅ UIと非同期処理の関心を分離


設計判断フロー

① 状態の変更がUIをまたぐ? → イベントで伝播

② ロジック同士が依存し合っている? → Pub/Subで分離

③ 非同期の副作用が混在している? → イベントチェーンで順序化

④ 増殖するイベント名が混乱している? → 命名規則で整理(機能/動作)

⑤ コンポーネントが“反応する”設計にしたい? → 購読によるリアクティブ構造

よくあるミスと対策

❌ すべての処理をイベント化して混乱

→ ✅ 状態管理 or イベント通知かを明確に区別


❌ イベント名が衝突・重複

→ ✅ 命名にドメイン階層を導入する


❌ 非同期イベント内で例外未処理

→ ✅ try/catch + エラー通知イベントを設計


結語

イベント駆動設計とは、“直接触らずに伝える”という技術的洗練である。

  • 状態は更新され
  • その変化は通知され
  • 関心を持つ者がそれに反応する

それぞれが独立しつつ、連携して動く――
その構造こそが、拡張可能で柔軟なアーキテクチャの基盤となる。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?