JavaScript | Web API:DOM 拡張 API - CustomEvent

JavaScript JavaScript
スポンサーリンク

CustomEvent は「自分で名前を付けられるオリジナルイベント」

CustomEvent は、
「クリックやスクロールみたいな“既存のイベント”ではなく、自分で名前を付けたイベントを作って、発火できる仕組み」 です。

clickinput はブラウザが発火してくれますが、
「カートに商品が追加された」「ゲームでスコアが更新された」みたいな、
アプリ固有の出来事には名前がありません。

そこで、

cart:add
score:changed
user:login

のように、自分でイベント名を決めて、
dispatchEvent で発火し、addEventListener で受け取る——
これを可能にするのが CustomEvent です。


基本形:「イベントを作る → 監視する → 発火する」

1. まずはイベントを受け取る側を書く

イベントは「聞く側」がいないと意味がないので、
先にリスナー(イベントハンドラ)を書きます。

document.addEventListener("hello", (event) => {
  console.log("hello イベントを受け取りました");
});
JavaScript

ここでは、"hello" という名前のイベントを待ち構えています。

2. CustomEvent を作って発火する

const ev = new CustomEvent("hello");
document.dispatchEvent(ev);
JavaScript

これで、さっき登録した "hello" リスナーが呼ばれます。

流れをまとめると、

  1. addEventListener("hello", ...) で待ち構える
  2. new CustomEvent("hello") でイベントオブジェクトを作る
  3. dispatchEvent で発火する

というシンプルなパターンです。

ここまでは「ただの合図」ですが、
本当においしいのは「イベントにデータを乗せられる」ところです。


重要ポイント①:detail で「イベントにデータを乗せる」

detail プロパティに好きなデータを入れられる

CustomEvent の第2引数にオプションを渡せます。
その中の detail が、「イベントに乗せるデータ」 です。

const ev = new CustomEvent("cart:add", {
  detail: {
    id: 123,
    name: "りんご",
    price: 200
  }
});
JavaScript

受け取る側では、event.detail からそのデータを取り出せます。

document.addEventListener("cart:add", (event) => {
  console.log("カートに追加されました:", event.detail);
});
JavaScript

これで、

イベント名 → 何が起きたかの種類
detail → その出来事に関する具体的な情報

という役割分担ができます。

小さな例:ボタンを押したら「カスタムイベントで通知」

HTML:

<button id="btn">商品を追加</button>

JavaScript:

const btn = document.querySelector("#btn");

document.addEventListener("cart:add", (event) => {
  console.log("カートイベント:", event.detail);
});

btn.addEventListener("click", () => {
  const ev = new CustomEvent("cart:add", {
    detail: {
      id: 1,
      name: "サンプル商品",
      price: 500
    }
  });
  document.dispatchEvent(ev);
});
JavaScript

ボタンをクリックすると、
"cart:add" イベントが発火し、
リスナーが event.detail を受け取ってログに出します。

ここで掴んでほしいのは、

「クリックという“物理的な操作”」と
「カートに商品が追加されたという“アプリの出来事”」を分けて考えられる

という感覚です。


重要ポイント②:どの要素でイベントをやり取りするか

document や window を「イベントバス」として使う

さっきの例では document に対して

document.addEventListener("cart:add", ...)
document.dispatchEvent(ev)

としていました。

これは、document を「全体のイベントの集約場所」として使うパターンです。

小さなアプリならこれで十分ですが、
コンポーネントごとにイベントを閉じたい場合は、
特定の要素を使うこともできます。

特定の要素をイベントの発信源にする

例えば、#cart 要素を「カートのイベントの窓口」にするならこうです。

HTML:

<div id="cart"></div>
<button id="btn">商品を追加</button>

JavaScript:

const cart = document.querySelector("#cart");
const btn = document.querySelector("#btn");

cart.addEventListener("cart:add", (event) => {
  console.log("cart 要素で受信:", event.detail);
});

btn.addEventListener("click", () => {
  const ev = new CustomEvent("cart:add", {
    detail: { id: 1, name: "サンプル商品" }
  });
  cart.dispatchEvent(ev);
});
JavaScript

このように、

「どの要素に向けてイベントを投げるか」
「どの要素でイベントを聞くか」

を設計できるのが、DOM イベントの面白いところです。


重要ポイント③:バブリング(親要素に伝わるかどうか)

bubbles: true で「親要素にも伝わる」イベントにできる

CustomEvent のオプションには、bubbles というフラグがあります。

const ev = new CustomEvent("hello", {
  bubbles: true
});
JavaScript

bubbles: true にすると、
そのイベントは「子 → 親 → さらに親…」と伝わっていきます(バブリング)。

例えば、こういう HTML を考えます。

<div id="parent">
  <button id="child">子ボタン</button>
</div>

JavaScript:

const parent = document.querySelector("#parent");
const child = document.querySelector("#child");

parent.addEventListener("hello", (event) => {
  console.log("親で hello を受け取りました");
});

child.addEventListener("click", () => {
  const ev = new CustomEvent("hello", { bubbles: true });
  child.dispatchEvent(ev);
});
JavaScript

ここでは、

child"hello" を発火
bubbles: true なので、イベントが親の parent にも届く
parent に登録したリスナーが呼ばれる

という流れになります。

これを使うと、

子コンポーネントが「何か起きたよ」とだけ知らせる
親コンポーネントがそれをまとめて受け取って処理する

という設計がしやすくなります。

detail と bubbles を組み合わせると「イベント駆動設計」がしやすい

例えば、

子が "todo:added" イベントを発火
detail に追加された TODO の内容を入れる
bubbles: true で親に伝える
親がそれを受け取ってリストに追加する

といった「イベント駆動」の流れを、
DOM イベントだけで組み立てられます。

初心者の段階では、

「bubbles: true にすると、親要素でも addEventListener で拾える」

くらいの理解で十分です。


実例:簡単な「カウンターコンポーネント」を CustomEvent でつなぐ

HTML

<div id="counter">
  <button id="inc">+1</button>
  <button id="dec">-1</button>
  <p id="value">0</p>
</div>

JavaScript

const counter = document.querySelector("#counter");
const inc = document.querySelector("#inc");
const dec = document.querySelector("#dec");
const value = document.querySelector("#value");

let current = 0;

// カスタムイベントを受け取る側
counter.addEventListener("counter:change", (event) => {
  current = event.detail.value;
  value.textContent = current;
});

// ボタン側:イベントを発火するだけ
inc.addEventListener("click", () => {
  const ev = new CustomEvent("counter:change", {
    detail: { value: current + 1 }
  });
  counter.dispatchEvent(ev);
});

dec.addEventListener("click", () => {
  const ev = new CustomEvent("counter:change", {
    detail: { value: current - 1 }
  });
  counter.dispatchEvent(ev);
});
JavaScript

ここでのポイントは、

ボタンは「カウンターが変わったよ」というイベントを投げるだけ
実際に値を更新して表示するのは、counter 要素のリスナー

という役割分担になっていることです。

このように、

「イベントを発火する側」と
「イベントを処理する側」を分けることで、
コードの見通しが良くなり、再利用もしやすくなります。


初心者として CustomEvent で本当に掴んでほしいこと

CustomEvent は「自分で名前を付けたオリジナルイベント」を作るための仕組み
new CustomEvent(name, { detail }) でイベントを作り、dispatchEvent で発火する
event.detail に好きなデータを乗せて渡せる
bubbles: true にすると、親要素にもイベントが伝わる(バブリング)
「クリックなどの物理イベント」と「アプリ固有の出来事」を分けて設計できる

まずは、

document に対して "hello" イベントを投げてみる
detail にオブジェクトを入れてログに出してみる

という小さな実験から始めてみてください。

「DOM は“イベントで会話できるオブジェクトたちの世界”なんだ」
という感覚が一度でも掴めると、
CustomEvent は単なる API ではなく、
アプリ全体を“イベントでつなぐ”ための、とても強力な道具に見えてきます。

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