以下のようなReact Componentを作成し、内側のsubmitボタンを押下した時の挙動が私の意図した挙動と異なったので調査しました。
具体的には内側のsubmitボタン押下時に内側のonSubmitのみならず外側のonSubmitが発火しました。
<Form>
<Button type="submit" />
<Dialog>
<Form>
<Button type="submit" />
</Form>
</Dialog>
</Form>
なおDialogコンポーネントはcreatePortal機能を利用していて、実際にレンダリングされるhtmlは下のようになると思ってください。
<form>
<button type="submit" />
</form>
<form>
<button type="submit" />
</form>
前提条件として、formはマークアップ的にnestするのはNGです。
なのでReact上でネストしたとしても正しく制御する位はcreatePortal等で外側へ移動する必要があります。
Content model:
https://html.spec.whatwg.org/multipage/forms.html#the-form-element
Flow content, but with noform
element descendants.
Reactのイベントバブリング
内側のsubmitは実際のhtmlではportalを利用することで独立したformになるため(ネストしてない)、内側のsubmitは上位へのsubmitへと紐付けができないはずです。にも関わらず実際には内外のsubmitが発火します。
この答えはcreatePortalにありました。
ポータルを介したイベントのバブリング
ポータルは DOM ツリーのどこにでも存在できますが、他のあらゆる点では通常の React の子要素と変わらずに振る舞います。コンテクスト (context) のような機能は、たとえ子要素がポータルであろうと全く同じように動きます。というのも、DOM ツリー上の位置にかかわらず、ポータルは依然として React のツリー内にいるからです。
これにはイベントのバブリングも含まれます。ポータルの内部で発火したイベントは React のツリー内の祖先へと伝播します。たとえそれが DOM ツリー上では祖先でなくともです。
https://ja.legacy.reactjs.org/docs/portals.html#event-bubbling-through-portals
これ何が書いてあるかというと、イベントのバブリングはDOMツリーではなくReactツリー上でバブリングするようにReactが上手く処理しているってことです。つまりDOMツリーの構造はユーザは意識しないくていいようにつなぎ込みをしてくれるんですね。
これ有難いような有難くないような2面性があると思います。
createPoralを使うシチュエーションでは当然DOMツリーを意識しているはずですが、イベントバブリングはDOMツリーを無視してReactツリーで考えないといけません。知っていれば柔軟に対応できるのですが、知ってないとイベントバブリングだけオリジナルのルートを通るって発想になりません。
コメント