Reactのイベントハンドリングについて
はじめに
この記事はReact #2 Advent Calendar 2019 11日目の記事です。
先日JSConfに参加し、今まであまり意識せずなんとなく使ってきたReactのイベントハンドリングについて個人的に新しい発見があったので、記事にしてみます。
本記事ではReactのイベントハンドリングの仕組みについてざっくりと理解することを目的としますが、より詳しい説明や、この仕組みによって puppeteer などでテストする際にこれによってどのような技術的チャレンジが行われているかはBenjamin Gruenbaumさんの The Anatomy of a Clickという発表から知ることができるのでぜひご視聴ください。
JavaScriptのイベント伝搬について
まずJavaScriptがイベントを取り扱うのかについて見ていきます。
通常クリックイベントをあるelement以下のDOMに付与しようとすると、以下のように書くと思います。
el.addEventListener('click', listener)
このように書くことによって、 el
以下のDOMがクリックされた時、listener
にそのイベントの発火が渡される形になります。このクリックからlistenerへの発火は、実はより細かく見ると、Capture Phase
Target Phase
Bubble Phase
というフェーズにわかれていて、上で記述した addEventListener
はBubbling Phase
でこのイベントリスナーが評価されるようになっている状態です。
逆にCapture Phase
でイベントの処理を行おうとする場合、以下のようにuseCapture
オプションをtrueにします。
el.addEventListener('click', listener, true)
こちらはW3C UIEvents specificationから引用した上記フェーズの説明図となります。
まずCapture Phase
では、Windowから順にその子要素を辿るようにイベントのターゲットである要素まで順々にイベントを処理していきます。
次にTarget Phase
では今伝搬しているイベントをstopPropagation
などで止めるかどうかなどを判定します。
最後にBubbling Phase
では、イベントターゲットから親の要素を辿りWindowまでイベントを伝搬していく形になります。
基本的にはBubbling
React的イベントハンドリング
ざっくりとJavaScriptにおけるイベントの仕組みについて把握したので、Reactではどのようにこのフェーズを取り扱っているのかを見ます。
Reactでは、そのイベントシステムの一部として、SyntheticEvent
というものが存在しています。
例えば、ReactのonClick
というSyntheticEvent
のイベントハンドラはユーザが起こすクリックに対するイベントの処理をBubbling Phase
で行うようになっており、Capture Phase
で処理を行おうとする場合は、onClickCapture
など、~Capture
というイベントハンドラを利用することでこれが可能になっています。
また、SyntheticEvent
はブラウザ間のイベントの仕様の違いなどを吸収してくれています。例えば、以下のようなonMouseEnter
のイベントを各種ブラウザでサポートするための以下リンク先の実装を見てみると
mouseOver
とmouseOver
イベントの組み合わせによってそれを判定していることがわかり、開発者があまり気にしなくても、クロスブラウザ対応をケアしてくれていることがわかるかと思います。
このような抽象化は開発者としてはとてもありがたいのですが、気をつけたいのは、これはonMouseEnter
をそのままmouseOver
とmouseOver
イベントの組み合わせに置き換えているため、直接onMouseEnter
を発火させた場合、Reactはそのイベントを処理することができないことがある可能性があるということです。テストなどを行う際に、この仕組みを知っていると、もしかしたらデバッグなどに役立つかもしれません。
まとめ
- JavaScriptのイベントの処理の仕組みには
Capture Phase
Target Phase
Bubbling Phase
という3つのフェーズが存在している - Reactは基本的には
Bubbling Phase
でイベントの処理をするようになっているが、Capture Phase
で処理するイベントハンドラも存在している - Reactは
SyntheticEvent
を使ってイベントを抽象化しているため、単純にイベントを同じような名前のイベントを直接発火させたとしても、反応しない場合があるので気をつける必要がある