React v16 まではイベントハンドラが window.document に登録されていたという話
@koba04 さんの React v17 の変更に関するこぼれ話 を読んで、コードを動かしてみたので書く。
v16 でイベントハンドラは document に登録される
React v17 の変更に関するこぼれ話 に記載されている通り、React v16 までは React 内のイベントハンドラは document に登録される
元記事のコード例 でgetEventListeners(window.document)
すると、window.document
に React のクリックイベントが登録されていることが分かる。
- なので
button
->body
じゃなくて - (
button
スキップ) ->body
->document
の順にイベントが伝搬する
ちなみに、コンポーネント内のイベントの親子関係は保たれるので
<div onClick={()=>{console.log("div click")}}> <button onClick={()=>{console.log("button click")}}>
のような場合は button
-> div
の順に実行される。
v17 で getEventListeners(window.document)
する
v17 では ReactDOM.render
の第2引数にイベントハンドラが登録されるので、getEventListeners(window.document)
すると空が返ってくる。
業務で直面した問題
結論としては特に↑で書いたこととは関係なかったが、こんな現象に業務で出会ったので余談として書いておく。
- React v16 を使用していて
- オーバーレイの子要素としてモーダルを設置
- オーバーレイ、モーダルのボタンにクリックイベントを設定
- モーダルのボタンをクリックするとオーバーレイに設定したイベントが優先して発火される(なんで??)
v16 までのイベントハンドラが document に登録される仕様のせいだったりするのかなーと思ったが、React コンポーネント内の onClick イベントしか使っていない。よくよく見ると、
- イベント自体はボタン -> オーバーレイの順に伝搬している
- ボタン押下時に API コール -> レスポンスに応じてモーダルを更新する処理が走る
- でも↑が非同期処理なので先にモーダル(とオーバーレイ)を閉じる処理が走り、そうするとモーダルの更新処理が反映されない
というオチだった。ちなみに、オーバーレイの子要素にすると、ダイアログのどこをクリックしても閉じてしまうので兄弟要素にするか、元記事でも紹介されていたように適切にクリック要素を判定する する実装が必要になる。