This article was translated from Japanese by AI and may contain inaccuracies. For the most accurate content, please refer to the original Japanese version.
React

イベントのバブリングは DOM ツリーではなく React ツリーに従う

イベントのバブリングとは、ある要素で発生したイベントがその親要素まで伝播することです。React でポータルを使用した場合、DOM ツリー状親子関係でなかったとしても、React ツリー上親子関係であればイベントがバブリングされます。

イベントのバブリングとは

ブラウザ上でイベントが発生したとき、キャプチャリングとバブリングの 2 つの段階に分けて動作します。キャプチャリングではまず最上位の親要素(通常 <html>)にイベントハンドラが登録されているか調べて、あればそれを実行します。次に内側の要素に写って同様のことを繰り返し、実際にイベントが発生した要素が到達するまで繰り返します。バブリングの段階ではキャプチャリングの逆のことが起こります。実際にイベントが発生した要素にイベントハンドラがあるか調べ、あればそれを実行し、これを最上位の親要素に到達するまで繰り返します。

eventflow

キャプチャリングは実際のコードの中ではほとんど使われません。onclickaddEventListener で追加されたイベントハンドラはキャプチャリングについて何も知らないからです。キャプチャリングフェーズでイベントをキャッチするには addEventListener の第 3 引数に true を渡す必要があります。

よく問題に挙げられるのはバブリングフェーズです。具体例を見てみましょう。

上記の例では <div> の子要素に <button> 要素が存在し、それぞれのイベントに onclick イベントハンドラが登録されてみます。<button> をクリックすると、<button> に登録されたアラートが表示された後に、<div> に登録されたアラートが表示されるかと思います。これはバブリングにより <button> 要素のイベントハンドラが実行された後、<button> の親要素までさかのぼり <div> のイベントハンドラが実行されたためです。

React におけるイベントバブリング

もちろん React においてイベントを登録している場合にも同様にバブリングが発生します。下記の例で確認してみてください。

<button> 要素は Button コンポーネントとして切り出されていますが、DOM の階層は変わらないため変わらずバブリングにより <button> のアラートと <div> のアラートが表示されます。

ポータルを使用した場合のイベントのバブリング

さて、ここまでで DOM の階層によりバブリングがされることがわかったかと思いますが、ポータル を使用した場合には少々特殊な挙動となります。

ポータルとは親コンポーネントの DOM 階層外にある DOM ノードに対して子コンポーネントをレンダリングする仕組みです。これは例えばモーダルのように z-index を気にする必要があるコンポーネントを要素の重なるを意識することなく使用するために使われることがあります。

さきほどの React のイベントバブリングの例をポータルを使用して作成した場合以下のとおりになります。

ここで <button> をクリックしたとき、どのような挙動になるでしょうか?前回の例とは異なり <button> 要素はポータルにより別の DOM 階層に配置されているので、もはや <button> 要素の親には onClick イベントハンドラを登録した <div> 要素は存在しません。見た目からもわかりますが、描画される DOM の構造は次のようになっています。

<body>
  <span id="root">
    <div></div>
  </span>
  <span id="modal">
    <button>Button</button>
  </span>
</body>

このことからイベントのバブリングは発生しないように思えます。しかし、実際には <button> 要素をクリックしたときバブリングにより <button> のアラートと <div> のアラートの両方が表示されます。

これはポータルは DOM ツリーのどこにも存在できますが、他のあら言うる点では通常の React の子要素と変わらず振る舞うためです。つまり、ポータルの内部で発火したイベントは DOM ツリー上の親要素でなくとも React ツリー上の親要素であれば伝播されるということです。

このことは一見直感に反するようにも思えますが、ポータルに本質的に依存することのない、より柔軟な抽象化が可能であること示していると説明されています。つまり、<Modal> の実装がポータルを使っているかどうか関係なく、親コンポーネントからは <Modal> コンポーネントのイベントを捕捉できるということです。