Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Reactのイベント委譲、合成イベントのラッピング、ルートからのバブリング。
ReactはイベントリスナーをDOMエレメントに直接アタッチしません。代わりにイベント委譲を使用します — ReactDOM.createRoot()が呼び出されたdivであるルートコンテナに単一のリスナーがアタッチされます。ネイティブイベントがルートにバブリングすると、Reactがそれを傍受し、SyntheticEventオブジェクトにラップし、Reactコンポーネントツリーを通じてディスパッチします。
JSXでonClick='{'handleClick'}'と書いても、ReactはそのDOMノードにaddEventListenerを呼び出しません。代わりに、「click」イベントのためにルートコンテナに単一のリスナーを登録します — コンポーネントごとではなく、アプリ全体で1回だけ行います。
ボタンをクリックすると、ブラウザはボタン上でネイティブのclickイベントを発火させ、それが実際のDOMツリーを通じてReactのリスナーが待機しているドキュメントルートまでバブリングします。
Reactはネイティブイベントをブラウザを問わず正規化されたイベントオブジェクトであるSyntheticEventにラップします。これはブラウザに関係なく同じAPIを公開します。古いReactバージョンでは、SyntheticEventsはパフォーマンスのためにプールされ再利用されていました。
Reactはターゲット要素からルートまでFiberツリーを走査し、各FiberにあるonClick(または他のイベント)ハンドラを呼び出します。これはブラウザのバブリングをシミュレートしますが、Reactの仮想ツリー上で行われます。
ネイティブイベントと同様に、ReactはonClickCaptureによるキャプチャフェーズハンドラをサポートします。これらはバブルフェーズハンドラ(ターゲット → ルートのボトムアップ)が発火する前に、トップダウン(ルート → ターゲット)で発火します。
ルートの1つのリスナーがそのタイプのすべてのイベントを処理します。すべてのインタラクティブな要素にリスナーをアタッチするよりもはるかに効率的です。
Reactのクロスブラウザイベントラッパー。ブラウザ間の非一貫性を正規化します。生のアクセスのためにnativeEventプロパティを公開します。
イベントがターゲット要素から祖先要素に向かって上方向に伝播します。ほとんどのReactイベントハンドラのデフォルトフェーズです。
イベントがバブリング前にルートからターゲットに向かって下方向に伝播します。キャプチャフェーズにはonClickCaptureを使用します。
Reactのシミュレートされたツリーを通じたイベントの伝播を停止します。e.nativeEventで呼び出さない限り、ネイティブイベントは停止しません。
1// React attaches ONE listener to the root — not to each button:2// root.addEventListener('click', reactHandler)34function App() {5 return (6 <div onClick={() => console.log('div — bubble')}>7 <button8 onClick={(e) => {9 console.log('button — bubble');10 // e.stopPropagation() would stop div from firing11 }}12 onClickCapture={() => console.log('button — capture')}13 >14 Click me15 </button>16 </div>17 );18}1920// Click order:21// 1. button — capture (capture phase, top-down)22// 2. button — bubble (bubble phase, bottom-up)23// 3. div — bubble (bubble phase, continues up)
イベント委譲によりReactは非常に効率的です — アプリ内にどれだけ多くのボタンがあっても、イベントタイプごとにネイティブイベントリスナーは1つだけです。またReactは動的に追加されたコンポーネントのイベントも傍受・処理でき、Reactの合成イベントシステムがすべてのブラウザで一貫して動作することを可能にします。
You add a native `document.addEventListener('click', closeMenu)` to close a dropdown on outside click. Inside the dropdown, you call `e.stopPropagation()` on the React onClick — but the menu still closes.
React's stopPropagation stops propagation within React's synthetic event system (which uses delegation at the root). But the native listener on `document` has already received the event since React 17+ attaches to `root`, not `document`. The native handler fires before React can stop it.
Use `e.nativeEvent.stopImmediatePropagation()` to stop the native event from reaching other native listeners, or better yet, replace the native listener with a React `onClickCapture` on a wrapper div. Keep event handling within React's system to avoid mixed delegation conflicts.
Takeaway: React's event system runs inside a single delegated listener at the root container. Mixing React events with native `addEventListener` creates ordering conflicts. Prefer keeping all event logic within React's system for predictable behavior.
Legacy code from React 16 calls `e.persist()` in event handlers to use the event in async callbacks. After upgrading to React 18, you see that removing `e.persist()` works fine — but no one on the team understands why.
React 16 used event pooling — SyntheticEvent objects were reused after the handler returned, setting all properties to null. Accessing `e.target` in a setTimeout would return null unless you called `e.persist()`. Teams kept adding persist() defensively everywhere.
React 17+ removed event pooling entirely. SyntheticEvent objects are no longer reused — they persist naturally. All `e.persist()` calls can be safely removed. Understanding this change helps teams clean up legacy code and stop defensive coding patterns that are no longer needed.
Takeaway: React's event system evolves across versions. Event pooling removal (React 17), root-level delegation (React 17), and automatic batching in event handlers (React 18) are all changes that affect how you write event handling code. Stay current with the release notes.