はじめに
とあるボタンにonClickイベント時の処理を設定した際、親要素にもonClickイベント時の処理を指定していたところ、「ボタンのonClick時の処理発火→その後親要素のonClick時の処理も発火」となってしまう事態が発生した。
簡単にコードを書くと、以下のような構成でボタンをクリックすると、コンソールに ボタンをクリック
と divをクリック
が出力されてしまうという状況。
<div onClick={() => console.log('divをクリック')}>
<button onClick={e => console.log('ボタンをクリック')}>
ボタン
</button>
</div>
ここで起きていた事態について、またその解決法について備忘録を書いておく。
※タイトルには【React】と書いたが、使用するのはJavaScriptの関数なのでReactだけの話では無い。
バブリングとは
先述した事態の正体は「バブリング」というもの。
親要素を持つ要素においてイベントが発火すると、現代のブラウザではデフォルトで「バブリング」というメカニズムが発動する。
その流れは以下の通り。
- 実際にクリックされた要素の
onClick
イベントハンドラが登録されていれば、それを実行する。 - 次に直上の親要素に移動して同じことを行い、またひとつ上の親要素へと、
<html>
に到達するまで繰り返される。
これと全く逆で、親要素から内側へ内側へとイベントが実行されていく「キャプチャリング」というメカニズムも存在するが、現代のブラウザではデフォルトで「バブリング」の流れでイベントを実行するらしい。
※詳しくはMDN参照。
イベントへの入門 - ウェブ開発を学ぶ | MDN
stopPropagation()で問題を解決する
バブリングが起きるうと困る場合の解決方法として、JavaScriptの stopPropagation()
という関数がある。
これの関数がイベントハンドラで実行されると、そのイベントハンドラは実行されるが、それより上位の親要素にはイベントが伝播しないようにできる。
使い方は以下の通り子要素のイベントハンドラの中でstopPropagation()
を実行するのみ。
こうすると、ボタンをクリックしたときに ボタンをクリック
のみがコンソールに出力される。
<div onClick={() => console.log('divをクリック')}>
<button
onClick={e => {
e.stopPropagation();
console.log('ボタンをクリック');
}}
>
ボタン
</button>
</div>
バブリングを活用する
気づかぬうちに意図しない挙動を引き起こしてしまって、バブリングなんてなければ良いのに、、、と思ったが、便利な利用方法もあるらしい。
イベント移譲という効果を利用すれば、例えば
「たくさんある子要素のどれかしらがクリックされた際に何らかのコードを実行したい」
という場面で、個々の子要素一つひとつにイベントリスナーを設定しなくても、親要素のイベントリスナーひとつを設定するだけで、子要素からバブリングしてくるイベントを受け取ることができる。
さいごに
このようにイベントが子要素から親要素へ伝播しているということにも今まで気付いていなかったが、知っていないと意図せぬ挙動を引き起こすかもしれないので気を付けたいと思った。
またイベント移譲ができることで便利な場面もあると思うので、ぜひ頭に置いておこうと思う。
参考記事