JavaScript | DOM 操作:イベント発展 – dispatchEvent

JavaScript
スポンサーリンク

dispatchEvent とは何か

dispatchEvent は「イベントを自分で発火させる」ためのメソッドです。DOM 要素や EventTarget に対して、既存のイベント(click など)や自作のカスタムイベント(CustomEvent)を投げられます。ここが重要です:dispatchEvent は“リスナーを呼ぶための合図”。ブラウザの標準動作(既定動作)まで必ずしも起こすわけではありません。既定動作を起こしたいときは element.click() のような“アクティベーション API”を使うのが基本です。


基本の使い方(Event/CustomEvent を作って発火)

最小の例(作る → 聴く → 発火)

<div id="bus"></div>
<script>
  const bus = document.getElementById("bus");

  // 聴く
  bus.addEventListener("hello", (e) => {
    console.log("受信:", e.detail?.msg);
  });

  // 作る(detail は CustomEvent で持てる)
  const ev = new CustomEvent("hello", { detail: { msg: "こんにちは" } });

  // 発火
  bus.dispatchEvent(ev);
</script>
HTML

ここが重要です:CustomEvent の detail は“文脈”を入れる場所。必要最小限の情報だけを渡すと受け手の自由度が高くなります。

既存イベントを合図だけで起こす(リスナー呼び出し)

<button id="btn">押す</button>
<script>
  btn.addEventListener("click", () => console.log("クリック処理"));
  btn.dispatchEvent(new Event("click", { bubbles: true })); // リスナーは呼ばれる
</script>
HTML

ここが重要です:dispatchEvent で発火した “合成イベント” は多くの既定動作を伴いません。button のクリックでフォーム送信など“ネイティブの振る舞い”を起こしたいなら btn.click() を使います。


返り値とキャンセル(cancelable/preventDefault)

キャンセル可能イベントの中止判定

<script>
  // cancelable: true のイベントは preventDefault で“中止”合図ができる
  const ev = new CustomEvent("confirm", { cancelable: true, detail: { id: 42 } });

  document.addEventListener("confirm", (e) => {
    const ok = confirm(`本当に #${e.detail.id} を実行しますか?`);
    if (!ok) e.preventDefault();
  });

  const succeeded = document.dispatchEvent(ev); // true:未キャンセル / false:キャンセルされた
  console.log("進める?", succeeded);
</script>
HTML

ここが重要です:dispatchEvent の返り値は“中止されたかどうか”。false なら、リスナーが preventDefault した合図です。分岐で安全にロジックを制御できます。


伝播の設計(bubbles/composed/target)

親で受けたいなら bubbles: true

<div id="outer"><button id="inner">子</button></div>
<script>
  outer.addEventListener("notify", (e) => {
    console.log("親で受信:", e.detail);
  });

  inner.dispatchEvent(new CustomEvent("notify", {
    detail: { msg: "子から" },
    bubbles: true
  }));
</script>
HTML

ここが重要です:bubbles: true で“バブル伝播”させれば、親一箇所でまとめて受けられます。イベント委譲の考え方と相性が良いです。

シャドウDOMを越えるなら composed: true

<script>
  // Web Components 内で発火しても、composed: true なら外へ届く
  const ev = new CustomEvent("change", { detail: { value: 1 }, bubbles: true, composed: true });
</script>
HTML

ここが重要です:Web Components を使う時は、外へ通知するか(composed: true)内に閉じるかを設計で決めます。

target / currentTarget の文脈

dispatchEvent で発火したイベントでも、e.target は“発火した要素”、e.currentTarget は“リスナーを持つ要素”です。ここが重要です:親で受ける設計(委譲)なら、判定は target、操作範囲の安全確認は currentTarget.contains(…) を使います。


ネイティブ既定動作との違い(click() との使い分け)

既定動作を起こす API と合成イベントの違い

<form id="f"><button id="submitBtn" type="submit">送信</button></form>
<script>
  // 合成イベント(dispatchEvent)は“リスナー”は動くが、送信など既定動作は保証されない
  submitBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));

  // 既定動作も含めて起こすなら、アクティベーション API を使う
  submitBtn.click(); // → 送信の既定動作(submit)が実行される
</script>
HTML

ここが重要です:ユーザー操作相当の“アクティベーション”は element.click() のような専用 API。dispatchEvent は“合図を投げる”ための低レベル機能だと理解して使い分けましょう。


実践パターン(完了合図、親集約、イベントバス)

処理完了をページへ通知(疎結合)

<form id="f"><input name="email" required><button>送信</button></form>
<script>
  f.addEventListener("submit", async (e) => {
    e.preventDefault();
    await new Promise(r => setTimeout(r, 300)); // 疑似送信
    document.dispatchEvent(new CustomEvent("user:saved", { detail: { email: f.email.value } }));
  });

  document.addEventListener("user:saved", (e) => {
    console.log("保存完了:", e.detail.email);
  });
</script>
HTML

ここが重要です:“完了”をイベントで表すと、表示側や通知側を差し替えても送信側を触らずに拡張できます。

子は合図だけ、親で集約処理(委譲)

<ul id="list"><li>りんご <button class="delete">削除</button></li></ul>
<script>
  list.addEventListener("item:delete", (e) => {
    e.target.closest("li")?.remove();
  });

  list.addEventListener("click", (e) => {
    const del = e.target.closest(".delete");
    if (!del || !list.contains(del)) return;
    del.dispatchEvent(new CustomEvent("item:delete", { bubbles: true }));
  });
</script>
``]
ここが重要です:子は“イベントを出すだけ”。親で集約すると、要素が増えてもコードが散らばりません。

### 簡易イベントバス(DOM をハブに)
```html
<div id="bus" hidden></div>
<script>
  const bus = document.getElementById("bus");
  const publish = (type, detail) => bus.dispatchEvent(new CustomEvent(type, { detail }));
  const subscribe = (type, fn) => bus.addEventListener(type, fn);

  subscribe("theme:change", (e) => document.documentElement.dataset.theme = e.detail);
  publish("theme:change", "dark");
</script>
HTML

ここが重要です:フレームワークなしでも、EventTarget と CustomEvent があれば疎結合の連携を作れます。


落とし穴と回避策(設計の勘所)

既定動作が“起きない”混乱

dispatchEvent は合成イベント。リンク遷移・フォーム送信などの既定動作は起きないことが多いです。ここが重要です:既定動作が必要なら element.click()/form.requestSubmit() を使う。

bubbles を指定し忘れて親で受けられない

親でまとめて受けたいなら { bubbles: true } を忘れない。ここが重要です:範囲は currentTarget.contains(…) で安全確認。

cancelable を付けずに“中止”判定したい

中止の権利をリスナーに与えたいなら { cancelable: true }。dispatchEvent の返り値で判定を必ず行う。

detail に巨大データを詰める

detail は最小限。大きなデータは ID を渡し、受け手が取得するほうが拡張に強い。

シャドウDOMで外に届かない

Web Components で外へ知らせたい時は { composed: true }。閉じたい設計なら false のままにする。


まとめ

dispatchEvent は「イベントを自前で発火する」ための基礎メソッドです。CustomEvent の detail で文脈を渡し、bubbles で親に集約、必要なら cancelable/composed を設計に合わせて使う。返り値で“中止”を判定し、既定動作が必要な場面では element.click()/form.requestSubmit() を選ぶ。疎結合の連携(完了合図・イベントバス・親集約)に活用すれば、初心者でも拡張しやすく壊れにくいイベント駆動の設計が書けます。

タイトルとURLをコピーしました