JavaScript 逆引き集 | イベントデリゲーション

JavaScript JavaScript
スポンサーリンク

イベントデリゲーションの基本 — parent.addEventListener('click', e => { if (e.target.matches('button')) ... })

イベントデリゲーションは、子要素それぞれにリスナーを付けず、親に1つだけ付けて「イベントの伝播」を利用して処理するテクニックです。動的に増えるボタンにも自動で対応でき、パフォーマンスと保守性が向上します。


仕組みとメリット

  • イベントの伝播(バブリング): クリックなどのイベントは、発生した要素から親→さらに親…と上に伝わります。親で受けて「どの子が押されたか」を判定します。
  • メリット:
    • 追加・削除に強い: 後から追加された子にも自動対応。
    • メモリ効率: リスナー数が1つで済む。
    • 保守性: 1箇所のロジックで全子要素の挙動を管理。

基本のコード例(ボタンのクリックをまとめて処理)

<div id="toolbar">
  <button data-action="save">保存</button>
  <button data-action="delete">削除</button>
  <button data-action="share">共有</button>
</div>
<script>
  const toolbar = document.getElementById("toolbar");

  toolbar.addEventListener("click", (e) => {
    const btn = e.target.closest("button");       // クリック元から最も近い button
    if (!btn || !toolbar.contains(btn)) return;   // 親内のbutton以外は無視

    const action = btn.dataset.action;
    switch (action) {
      case "save":   console.log("保存します"); break;
      case "delete": console.log("削除します"); break;
      case "share":  console.log("共有します"); break;
    }
  });
</script>
HTML
  • ラベル: closest("button") でボタンを安全に特定し、dataset で処理を切り替え。

よく使うテンプレート集

リストの項目クリックで選択(委譲)

const list = document.getElementById("list");
list.addEventListener("click", (e) => {
  const item = e.target.closest("li.item");
  if (!item || !list.contains(item)) return;
  item.classList.toggle("selected");
});
JavaScript
  • ラベル: 子の追加・削除に強く、全項目のクリックを親で一括管理。

アイコンごとに別アクション(多ターゲット)

const cards = document.getElementById("cards");
cards.addEventListener("click", (e) => {
  const like = e.target.closest(".icon-like");
  const del  = e.target.closest(".icon-delete");
  const card = e.target.closest(".card");
  if (!card || !cards.contains(card)) return;

  if (like) { card.classList.toggle("liked"); }
  else if (del) { card.remove(); }
});
JavaScript
  • ラベル: 同じ親で複数のターゲットクラスを判定して分岐。

フォーム内の動的ボタンに対応(追加してもOK)

const form = document.getElementById("profile");
form.addEventListener("click", (e) => {
  const addPhone = e.target.closest("[data-add='phone']");
  if (!addPhone) return;
  const field = document.createElement("input");
  field.name = "phone";
  field.placeholder = "電話番号";
  form.querySelector(".phones").appendChild(field);
});
JavaScript
  • ラベル: 後から追加したボタンでも動く。個別の addEventListener は不要。

使い分けのポイント(委譲 vs 直接リスナー)

  • 委譲が向く場面:
    • 要素が増減する: リストやカードが動的に生成される。
    • 数が多い: 子が大量にあり、個別リスナーは負担。
    • 一括ロジック: 同じ挙動をまとめて管理したい。
  • 直接リスナーが向く場面:
    • 単発の要素: 1〜数個で増減しない。
    • 厳密な発火位置: バブリングの影響を避けたい特殊ケース。

実務でのコツ

  • ターゲット特定は closest が安全:
    • ネストされた要素内のクリックでも、目的の親ボタン/項目を拾える。
  • スコープ防御:
    • parent.contains(target) で親の外からのイベント混入を避ける。
  • 選択の基準はクラスや data-属性:
    • data-action.item は意味が明確で、テスト・保守が楽。
  • パフォーマンス:
    • 親を細かく分けて委譲すると、無駄な判定が減る。大きな document より近い親を使う。

ありがちなハマりポイントと対策

  • テキストクリックで e.target が想定外:
    • 対策: e.target.closest("button") で安全に“ボタン”を得る。
  • 親外のクリックが引っかかる:
    • 対策: if (!parent.contains(target)) return; を入れる。
  • stopPropagation の誤用:
    • 対策: 不要に伝播を止めると委譲が効かない。必要な場面だけ使う。
  • 動的要素に個別リスナーを足し続ける:
    • 対策: 委譲に切り替える。増えるほど効果がある。

練習問題(手を動かして覚える)

<ul id="list">
  <li class="item">Apple <button class="del">削除</button></li>
  <li class="item">Banana <button class="del">削除</button></li>
</ul>
<button id="add">追加</button>
<script>
  const list = document.getElementById("list");
  const add  = document.getElementById("add");

  // 1) 削除ボタンのクリックを親で処理
  list.addEventListener("click", (e) => {
    const del = e.target.closest(".del");
    if (!del || !list.contains(del)) return;
    const li = del.closest("li.item");
    li.remove();
  });

  // 2) 追加ボタンで新しい項目を動的生成(委譲だからイベントは不要)
  add.addEventListener("click", () => {
    const li = document.createElement("li");
    li.className = "item";
    li.innerHTML = `New <button class="del">削除</button>`;
    list.appendChild(li);
  });

  // 3) 項目クリックで選択トグル(同じ親で別動作)
  list.addEventListener("click", (e) => {
    const item = e.target.closest("li.item");
    if (!item || !list.contains(item)) return;
    if (!e.target.closest(".del")) { // 削除ボタン以外のクリックのみ
      item.classList.toggle("selected");
    }
  });
</script>
HTML

直感的な指針

  • 親に1つだけリスナーを置き、closestdataset/クラス でターゲット判定。
  • 動的に増えるUIや大量要素は委譲が効率的。スコープ防御と分岐を丁寧に。
  • 個別リスナーが増えてきたら、委譲へ切り替えるサイン。
タイトルとURLをコピーしました