CustomEvent は「自分で名前を付けられるオリジナルイベント」
CustomEvent は、
「クリックやスクロールみたいな“既存のイベント”ではなく、自分で名前を付けたイベントを作って、発火できる仕組み」 です。
click や input はブラウザが発火してくれますが、
「カートに商品が追加された」「ゲームでスコアが更新された」みたいな、
アプリ固有の出来事には名前がありません。
そこで、
cart:addscore:changeduser: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" リスナーが呼ばれます。
流れをまとめると、
addEventListener("hello", ...)で待ち構えるnew CustomEvent("hello")でイベントオブジェクトを作る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
});
JavaScriptbubbles: 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 ではなく、
アプリ全体を“イベントでつなぐ”ための、とても強力な道具に見えてきます。
