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におけるイベント設計戦略:バブリング・キャプチャ・委譲と管理可能なイベント構造

Posted at

概要

DOMイベントは、**ユーザー操作に応答するアプリケーションの“神経系”**である。
しかし、安易な addEventListener の多用や伝播の誤解は、予測不能なバグやメモリリーク、非効率なUI構造を引き起こす。

本稿では以下を体系化する:

  • バブリング・キャプチャの仕組みと使い分け
  • イベント委譲による効率化
  • 明示的なイベント設計のパターン
  • リスナー登録・解除の責務と設計
  • カスタムイベントの活用と分離設計

1. イベントの伝播:キャプチャとバブリング

✅ 3段階の流れ

1. キャプチャフェーズ(親 → 子)
2. ターゲットフェーズ(対象要素)
3. バブリングフェーズ(子 → 親)
element.addEventListener('click', handler, true);  // キャプチャ
element.addEventListener('click', handler, false); // バブリング(デフォルト)

→ ✅ 明示的に true/false を指定して意図を明文化する


2. バブリング制御と停止

element.addEventListener('click', (e) => {
  e.stopPropagation(); // ✅ バブリングを止める
  e.preventDefault();  // ✅ デフォルト動作の抑制
});
  • stopPropagation() → 他のリスナーに伝播させない
  • preventDefault() → リンク遷移、フォーム送信などをキャンセル

3. イベント委譲の設計(イベントバインドの最適化)

❌ 各要素にリスナーを直付け

buttons.forEach(btn => btn.addEventListener('click', onClick));

→ 要素が動的に増減する場合、メンテ不能・非効率


✅ 親要素に1つのリスナー(委譲)

document.querySelector('#list').addEventListener('click', (e) => {
  if (e.target.matches('.list-item')) {
    handleItemClick(e.target.dataset.id);
  }
});
  • ✅ 子要素にクラスやdata属性を付けて判別
  • ✅ 親に1つだけバインド → 高効率・柔軟

4. removeEventListenerの正しい設計

❌ 無名関数では解除不可

element.addEventListener('click', () => console.log('clicked'));
element.removeEventListener('click', () => console.log('clicked')); // ❌ 効かない

→ ✅ 明示的な関数定義が必要

function onClick(e) { ... }
element.addEventListener('click', onClick);
element.removeEventListener('click', onClick);

5. 一度だけ実行するイベント

element.addEventListener('click', handler, { once: true });

→ ✅ once: true で自動的に1回限りの実行に(手動で解除不要)


6. カスタムイベントとコンポーネント分離

const event = new CustomEvent('user:created', {
  detail: { id: 123 }
});

element.dispatchEvent(event);

element.addEventListener('user:created', (e) => {
  console.log(e.detail.id); // 123
});
  • ✅ UIコンポーネント間の通信に最適
  • ✅ “発火”と“受信”を構造的に分離できる

7. イベント命名・スコープ設計指針

設計指針 解説
click, input DOMの基本イベント
xxx:ready 状態通知(カスタムイベント)
user:created エンティティ指向のイベント名設計
グローバル禁止 document/window直付けは極力避ける
コンポーネント内 独自要素 or Shadow DOM内で閉じるべき

設計判断フロー

① 要素が動的に増える? → イベント委譲

② stopPropagation は必要? → 他のハンドラとの関係を設計

③ 一度しか使わないイベント? → once: true を使う

④ イベント解除が必要? → 関数を変数に束縛して管理

⑤ 複数コンポーネント間で通信? → CustomEvent で疎結合化

よくあるミスと対策

❌ 複数バインド → イベントの多重発火

→ ✅ removeEventListener で常に解除戦略を持つ


❌ stopPropagation の乱用

→ ✅ 原因を切るより“構造を見直す”


❌ 同じリスナーが複数バインド

// ループやクリック内でaddEventListenerし続ける

→ ✅ フラグ管理やonceオプションで設計を


結語

イベントとは「ユーザーの意図に対する、アプリケーション側の反応の設計」である。

  • DOM構造と伝播構造は一致しない
  • 発火点と処理点は分けて考える
  • バブリング・キャプチャは“構造上の意味”を持つ
  • 再利用されるイベント構造には、明示と設計が必要

“ただ反応する”ではなく、“意図に応じて設計された反応”を構築すること。
それが、モダンなUIの神経設計である。

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?