Reactでイベントバブリングとキャプチャリングを理解!初心者向けイベントハンドリング解説
生徒
「先生、Reactでボタンをクリックすると親のイベントも発火してしまいます。これはなぜですか?」
先生
「それはイベントバブリングが起きているからです。子要素のイベントが親要素に伝わっていく現象のことです。」
生徒
「イベントバブリングって聞きなれません。簡単に説明してもらえますか?」
先生
「簡単に言うと、水面に投げた小石の波紋のように、子要素のイベントが親要素やその上の要素まで順番に伝わる動きです。」
生徒
「じゃあキャプチャリングって何ですか?」
先生
「キャプチャリングはバブリングとは逆で、親要素から子要素へ順番にイベントを処理することです。」
生徒
「なるほど、ではReactでどう使うのか例を見せてください。」
先生
「では実際にコードで確認してみましょう。」
1. イベントバブリングの基本例
Reactでは子要素でクリックイベントが発生すると、自動的に親要素にもそのイベントが伝わります。これがイベントバブリングです。親要素と子要素両方で同じ処理をしたくない場合には、event.stopPropagation()を使ってバブリングを止めることができます。
import React from "react";
function App() {
const handleParentClick = () => {
alert("親要素がクリックされました");
};
const handleChildClick = (event) => {
event.stopPropagation();
alert("子要素がクリックされました");
};
return (
<div onClick={handleParentClick} style={{ padding: "50px", backgroundColor: "#eee" }}>
親要素
<button onClick={handleChildClick}>子要素ボタン</button>
</div>
);
}
export default App;
2. キャプチャリングの利用
通常、Reactのイベントはバブリングで伝わりますが、キャプチャリングを使うと親から順にイベントを処理できます。これを指定するには、onClickCaptureのように「Capture」を付けたイベントハンドラを使います。
import React from "react";
function App() {
const handleParentCapture = () => {
alert("キャプチャリング:親要素");
};
const handleChildClick = () => {
alert("子要素クリック");
};
return (
<div onClickCapture={handleParentCapture} style={{ padding: "50px", backgroundColor: "#cfc" }}>
親要素
<button onClick={handleChildClick}>子要素ボタン</button>
</div>
);
}
export default App;
3. バブリングとキャプチャリングの使い分け
バブリングは子要素で発生したイベントを親要素でも処理したいときに使います。逆に、キャプチャリングは親要素から先に処理を行い、子要素の処理を制御したい場合に便利です。状況に応じてonClickとonClickCaptureを使い分けると、複雑なUIでも効率的にイベントを管理できます。
また、stopPropagationでバブリングを止めることもできるため、意図しないイベントの伝播を防ぐことができます。これにより、Reactアプリで親子関係のある要素のイベント制御を柔軟に行えます。
例えば、モーダルの背景クリックで閉じる処理を行う場合や、リスト項目のクリックで詳細を表示する場合など、イベントの伝播の理解は非常に重要です。
4. stopPropagationの注意点とよくある勘違い
event.stopPropagation()は、イベントバブリング(子から親へ伝わる流れ)を止めるために使います。ただし「クリック自体を無効にする」わけではなく、あくまで親要素へイベントが伝播しないようにするためのメソッドです。
そのため、子要素のonClickは通常通り実行されますし、同じ要素に別の処理があればそれも動きます。「親のクリックだけ止めたい」「意図しない親のイベント発火を防ぎたい」というケースで使うと効果的です。
また、親側でonClickCaptureを使っている場合、キャプチャリングは親→子の順で処理されるため、子要素側でstopPropagationを実行しても、親のキャプチャ処理はすでに走っている点に注意が必要です。イベントバブリングとキャプチャリングの順番を理解しておくと、Reactのイベントハンドリングで迷いにくくなります。
5. モーダルやメニューでのイベント伝播の活用例
ReactでよくあるUIとして、モーダルやドロップダウンメニューがあります。たとえば「背景をクリックしたらモーダルを閉じる」処理を親要素に置き、モーダル本体のクリックでは閉じないようにする、といった実装がよく行われます。
このとき、背景(親)にonClickを設定し、モーダル本体(子)でstopPropagationを使うと、モーダル内部をクリックしても背景クリック扱いにならず、意図しない閉じる動作を防げます。イベントバブリングを理解していると、こうした親子要素のクリック制御を自然に設計できます。
さらに、リスト項目のクリックで詳細画面へ遷移しつつ、項目内の削除ボタンだけは別処理にしたい場合なども、イベント伝播の制御が役立ちます。Reactのクリックイベントが親に伝わる仕組みを押さえることで、複雑な画面でも動作を安定させやすくなります。
6. Reactのイベントハンドラ命名と設計のコツ
イベントバブリングやキャプチャリングを扱う場面では、イベントハンドラの役割が増えるため、命名と設計が重要になります。たとえば親要素ならhandleParentClick、子要素ならhandleChildClickのように、どの要素の処理かが伝わる名前にすると読みやすくなります。
また、キャプチャリングを使う場合はhandleParentCaptureのように「Capture」を含めておくと、onClickとの違いが明確になります。ReactではonClickとonClickCaptureが混在すると挙動の理解が難しくなるため、命名で意図が分かるだけでもトラブルを減らせます。
さらに、親子でクリック処理が関わるUIでは「親でまとめて処理したいのか」「子だけで完結させたいのか」を先に決めると整理しやすいです。イベントの伝播を前提に考えることで、Reactアプリのイベントハンドリングをスムーズに組み立てられます。
まとめ
今回の記事では、Reactにおけるイベントハンドリングの要である「イベントバブリング」と「キャプチャリング」について詳しく解説してきました。Web開発、特にReactのようなコンポーネントベースのライブラリを使用していると、意図しない場所でクリックイベントが発生したり、親要素の処理が走り出したりといった現象に必ず遭遇します。これらはブラウザの仕様である「イベント伝播(Event Propagation)」の仕組みによるものですが、正しく理解して制御することで、より洗練されたユーザーインターフェースを構築することが可能になります。
イベント伝播のメカニズムを振り返る
イベントが要素に到達し、そこから抜けていくまでには、大きく分けて3つのフェーズが存在します。
- キャプチャリングフェーズ:イベントが最上位の要素(windowやdocument)からターゲット要素に向かって降りていく段階です。
- ターゲットフェーズ:実際にイベントが発生した要素でイベントが発火する段階です。
- バブリングフェーズ:イベントがターゲット要素から再び親要素、最上位要素へと昇っていく段階です。
Reactの標準的なイベント属性(onClickなど)は、このうち「バブリングフェーズ」で実行されます。逆に、親から順に処理を行いたい特殊なケースでは、onClickCaptureなどの「Capture」が付いた属性を利用することで、キャプチャリングフェーズでの処理が可能になります。
実践的なコードでの応用:イベントの停止
最も頻繁に利用されるテクニックは、event.stopPropagation()による伝播の停止です。例えば、リスト全体をクリックすると詳細ページへ飛ぶという設計の中で、そのリスト内にある「お気に入りボタン」だけは別の挙動をさせたい、といった場合に非常に有効です。
import React, { useState } from "react";
function EventStopExample() {
const [log, setLog] = useState("");
const handleParentClick = () => {
setLog("親要素(カード全体)のイベントが発火しました。");
};
const handleChildClick = (e) => {
// これを記述しないと、親要素のクリックイベントも動いてしまう
e.stopPropagation();
setLog("子要素(ボタン)のイベントが発火しました。伝播は止まっています。");
};
return (
<div
onClick={handleParentClick}
style={{
border: "2px solid #333",
padding: "20px",
borderRadius: "8px",
cursor: "pointer",
backgroundColor: "#f9f9f9"
}}
>
<h3 className="fs-6">クリック可能なカード</h3>
<p>カードのどこかをクリックしてみてください。</p>
<button
onClick={handleChildClick}
className="btn btn-primary"
>
ボタンだけをクリック
</button>
<div className="alert alert-secondary mt-3">
ログ:{log}
</div>
</div>
);
}
export default EventStopExample;
より高度な制御:キャプチャリングの活用シーン
日常的な開発でキャプチャリングを意識することは少ないかもしれませんが、特定の監視やログ収集、あるいは「子要素のいかなるクリックよりも先に親が介入したい」という場合には非常に強力なツールとなります。
例えば、アクセシビリティの向上や、特定の条件下で子要素の操作を一括で無効化したい場合などにonClickCaptureが役立ちます。Reactは仮想DOMを通じてこれらの標準的なブラウザイベントを「合成イベント(SyntheticEvent)」として最適化して提供しているため、ブラウザ間の差異を気にすることなく、一貫した実装ができるのが大きなメリットです。
最後に:設計の際のポイント
イベントのバブリングを利用することは、決して悪いことではありません。むしろ「イベントデリゲート(イベント委任)」という手法では、親要素で子要素のイベントを一括管理することでメモリ消費を抑え、パフォーマンスを向上させることができます。大切なのは、「今、イベントがどこから来てどこへ向かっているのか」を常に意識して、明示的にコードを書くことです。
Reactでの開発において、状態(State)の管理と同じくらい重要なのが、このイベントの管理です。コンポーネントが複雑になればなるほど、イベントの伝播を制する者がUIの挙動を制すると言っても過言ではありません。ぜひ、今回学んだバブリングとキャプチャリングを、皆さんのプロジェクトで活用してみてください。
生徒
「先生、ありがとうございました!イベントバブリングとキャプチャリングの違いが、図をイメージするように理解できました。特にevent.stopPropagation()が、波紋が広がるのを途中で止める防波堤のような役割なんだなって思いました。」
先生
「その例えは非常に分かりやすいですね!まさにその通りです。水面に投げた石の波紋(バブリング)を、必要ないところまで広げないように制御するのが、フロントエンドエンジニアの腕の見せ所ですよ。」
生徒
「実際にコードを書いてみると、onClickCaptureを使う場面は少なそうですが、親から先にチェックを入れたい時には便利そうですね。あと、Reactのドキュメントで『合成イベント』という言葉を見かけたのですが、これも関係ありますか?」
先生
「鋭いですね。Reactはブラウザ固有のイベントをそのまま使うのではなく、SyntheticEventという独自のラッパーで包んでいます。これによって、どのブラウザでも同じようにバブリングやキャプチャリングが動作するように保証されているんです。仕組みを知っておくと、デバッグの時に役立ちますよ。」
生徒
「なるほど、Reactが裏側で守ってくれているんですね。でも、まずは基本のonClickと、困ったときのstopPropagation、そして親から処理するonClickCaptureの3つをしっかり使い分けられるように練習してみます!」
先生
「素晴らしい意気込みです。特にモーダルウィンドウの作成や、複雑なフォームの実装ではこの知識が必須になります。もし挙動がおかしいな?と思ったら、まずは『今、イベントはバブリングしている最中かな?』と疑ってみる癖をつけてみてくださいね。」