◆ リスナは各要素ではなく document でまとめて処理
  ◆ イベントオブジェクトが React のものなので stopPropagation なども独自のもの
  ◆ document にまとめてもツリー構造のときのように順番やキャンセル制御できてる
  ◆ なぜか各要素にもリスナがつけられていて関数自体は何もしない noop
◆ Preact では複雑な仕組みはなくて各要素にそのままリスナ設定
  ◆ イベントオブジェクトはブラウザネイティブのもの

React 17 の変更点が出てたのでどんなものか見ていたら 知らなかった内部挙動について知れました
https://reactjs.org/blog/2020/08/10/react-v17-rc.html

17 自体は機能的には変更がなくて内部的な変更のみらしいです

イベントリスナの登録先

現在の React ではイベントリスナをつけるとき 実際にリスナを設定したい要素につけず document に設定してるらしいです
配列の要素数だけ繰り返し表示するものとかで何百とある場合に全部にリスナをつけるとムダが多いですからね
React に限らず普通の DOM 操作をするときでも そういう場所は共通の親要素に 1 つだけ設定するとかしてムダを省いていました

jQuery ユーザに多かった印象ですが 共通の親ではなく全部を document につけるという人もいて これだとツリー構造でイベントバブリングが起きるメリットを捨てています
そのせいで document にイベントリスナをつけるのは嫌いだったのですが React の場合は event オブジェクト自体がブラウザ native ではなく React 固有のものになっていて stopPropagation などのメソッドも React 独自のものになっていました
その処理のおかげで この内部挙動は知らなくても 直接各要素にリスナがついてる場合と同じような挙動にしてくれています

しかし 複数の React のバージョンが 1 つのページにあるとどちらもリスナは document についていて 他方のイベントを管理できないので 各要素にリスナを設定した場合と同じ挙動にできないみたいです
その解決のために 17 ではリスナ登録先を document から React の root に変えるのだとか
1 バージョンだけでも React 外で DOM 操作もする場合もあり 発火順序とかもありますし こっちのほうが良さそうです

動きを確認してみる

React 16.13.1 で実際に確認してみました

<div onClick={f1}>
div
<button onClick={f2}>A</button>
<button onClick={f3}>B</button>
</div>

こういう HTML 構造とリスナをセットして devtools の Event Listeners から設定されたリスナを見てみます
すると

click
document react-dom.development.js:4166
div react-dom.development.js:5768
button react-dom.development.js:5768

document にもリスナがありますが 各要素にもリスナがついています
種類が違うようなので まずは div や button 要素に付いてる方を見てみると

function noop() {}

何もしない関数がセットされていました

document の方は

function dispatchDiscreteEvent(topLevelType, eventSystemFlags, container, nativeEvent) {
flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, container, nativeEvent);
}

で こっちで処理してるようです

document のほうで処理してるのはわかりましたが 何もしない noop を付ける意味はあるのでしょうか
余計なリスナ登録を減らすために document にまとめてるなら noop を各要素につけてしまうと意味がないような気がします

devtools で見たときにリスナが設定されているということをわかるようにするためでしょうか?
それなら本番用ビルドなら消えているのかと試しましたが 本番用でも noop 関数がリスナに設定されていました

Preact の場合

私が使うのは React ではなく Preact の方なので こっちも調べてみました
Preact では特にこうした仕組みはなく 直接各要素にリスナがつけられていました
そもそも Preact ではリスナが受け取る event オブジェクトは独自のものではなくブラウザ native のものです
変に複雑なことしていないこっちのほうがシンプルで個人的にはやっぱり Preact のほうが好きです