Reactのイベントハンドラーが実際にブラウザでどのようなイベントにマッピングされるか、およびブラウザ間の互換性について詳しく解説します。
// React 16以前:documentにイベント委譲
document.addEventListener('click', reactEventHandler);
// React 17以降:rootElementにイベント委譲
rootElement.addEventListener('click', reactEventHandler);
影響範囲:
- 手動でdocumentにイベントリスナーを追加している場合の実行順序変更
- ポータル(Portal)との相互作用の変化
- 複数Reactアプリが同一ページにある場合の挙動変更
// Automatic Batching の導入
// イベント処理のバッチング方法が変更
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 18: 自動的にバッチング(1回の再レンダリング)
// React 17: 2回の再レンダリング
});
Concurrent Features による将来の変更可能性
- イベント処理の優先度制御
// 将来的な可能性(仮想的な例)
<input
onChange={handleChange}
priority="user-blocking" // 高優先度
/>
<div
onScroll={handleScroll}
priority="background" // 低優先度
/>
- 時間分割処理(Time Slicing)との連携
// 現在:同期的なイベント処理
const handleClick = () => {
// 重い処理が全て同期実行
processHeavyData();
updateUI();
};
// 将来的な可能性:中断可能な処理
const handleClick = () => {
startTransition(() => {
// 中断可能な処理として実行
processHeavyData();
updateUI();
});
};
- Suspense との統合
// 将来的な可能性
const handleAsyncAction = async () => {
// イベントハンドラー内でのSuspense制御
return <Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>;
};
// ✅ 推奨:React標準のイベントハンドラー
<button onClick={handleClick}>
// ❌ 避ける:直接的なDOM操作
useEffect(() => {
document.addEventListener('click', handler);
}, []);
- Profilerでイベント処理のパフォーマンス監視
- Components タブでイベントフローの確認
- 新バージョンでの回帰テスト実施
// 機能フラグを使用した段階的移行
const useNewEventHandling = process.env.REACT_VERSION >= 18;
function Component() {
if (useNewEventHandling) {
return <NewEventComponent />;
}
return <LegacyEventComponent />;
}
// 自動化されたイベント処理テスト
describe('Event Handling Compatibility', () => {
it('should handle click events consistently', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick} />);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
-
React Blog: 新機能・変更点の公式発表
-
RFC (Request for Comments): 将来の仕様検討
-
GitHub Issues: 実装詳細の議論
// 警告・エラーの監視
console.warn = (originalWarn => {
return (...args) => {
if (args[0]?.includes('React')) {
// React関連の警告をログ収集
logToMonitoring('react-warning', args);
}
originalWarn(...args);
};
})(console.warn);
ブラウザ |
イベント |
互換性イベント |
注意点 |
Chrome/Firefox/Safari |
click |
auxclick |
auxclickは中ボタン・右ボタン用 |
IE11 |
click |
- |
auxclick未対応 |
Edge |
click |
auxclick |
モダンブラウザと同様 |
Reactの内部処理:
// React内部的には以下のイベントを統合
- click (左クリック)
- auxclick (中・右クリック)
- キーボードでのアクティベート (Enter/Space)
ブラウザ |
イベント |
互換性イベント |
注意点 |
モダンブラウザ |
wheel |
- |
標準仕様 |
古いWebKit |
mousewheel |
- |
deltaのプロパティ名が異なる |
古いFirefox |
DOMMouseScroll |
- |
非標準、現在は非推奨 |
IE9+ |
wheel |
mousewheel |
IE9-11での挙動差異あり |
Reactの互換性処理:
// Reactが内部で正規化
wheel → { deltaX, deltaY, deltaZ }
mousewheel → { wheelDeltaX, wheelDeltaY } → 正規化
DOMMouseScroll → { detail } → 正規化
ブラウザ |
イベント |
互換性イベント |
注意点 |
全ブラウザ |
scroll |
- |
windowとelementで挙動差異 |
iOS Safari |
scroll |
- |
慣性スクロール中は発火頻度が異なる |
Android |
scroll |
- |
タッチスクロール時の発火タイミング差異 |
ブラウザ |
イベント |
互換性イベント |
key プロパティ |
code プロパティ |
注意点 |
Chrome/Firefox/Safari |
keydown /keyup
|
- |
✅ |
✅ |
標準仕様 |
IE11 |
keydown /keyup
|
- |
❌ |
❌ |
keyCode のみ |
Edge Legacy |
keydown /keyup
|
- |
⚠️ |
⚠️ |
部分対応 |
Reactの互換性処理:
// React SyntheticKeyboardEvent での正規化
event.key // 標準化された文字・キー名
event.code // 物理キーの識別子
event.which // レガシー対応(非推奨)
event.keyCode // レガシー対応(非推奨)
ブラウザ |
状況 |
代替手段 |
全ブラウザ |
非推奨化進行中 |
onKeyDown を使用 |
Firefox |
部分的に削除済み |
- |
Chrome |
段階的廃止 |
- |
要素タイプ |
イベント |
互換性イベント |
発火タイミング |
Reactの処理 |
<input type="text"> |
input |
change |
値変更時 |
input + change を統合 |
<select> |
change |
- |
選択変更時 |
change イベント |
<textarea> |
input |
change |
テキスト変更時 |
input + change を統合 |
<input type="checkbox"> |
change |
- |
チェック変更時 |
change イベント |
Reactの特殊処理:
// ブラウザのネイティブchangeは要素がフォーカスを失った時
// ReactのonChangeは入力中にリアルタイムで発火
<input onChange={...} /> // React版:リアルタイム
vs
document.querySelector('input').onchange = ... // ネイティブ:フォーカス時
onTouchStart / onTouchMove / onTouchEnd
プラットフォーム |
イベント |
互換性イベント |
対応状況 |
注意点 |
iOS Safari |
touchstart /touchmove /touchend
|
- |
✅ 完全対応 |
標準的な動作 |
Android Chrome |
touchstart /touchmove /touchend
|
- |
✅ 完全対応 |
標準的な動作 |
Windows Chrome |
touchstart /touchmove /touchend
|
pointerdown /pointermove /pointerup
|
✅ 対応 |
タッチデバイス必須 |
デスクトップ |
- |
mousedown /mousemove /mouseup
|
❌ 発火しない |
マウスイベントを使用 |
ポインターイベントとの関係:
// モダンな手法:pointer events(React 17+で改善)
onPointerDown // タッチ・マウス・ペン統合
onPointerMove // より統一的なAPI
onPointerUp // クロスプラットフォーム対応
イベント |
Chrome |
Firefox |
Safari |
IE11 |
互換性イベント |
play |
✅ |
✅ |
✅ |
✅ |
- |
pause |
✅ |
✅ |
✅ |
✅ |
- |
ended |
✅ |
✅ |
✅ |
✅ |
- |
loadstart |
✅ |
✅ |
✅ |
⚠️ |
loadstart (IE9+) |
canplay |
✅ |
✅ |
✅ |
✅ |
- |
Safari特有の注意点:
// iOS Safariでは自動再生に制限
<video onPlay={...} muted playsInline /> // 必要な属性
イベント |
イベント名 |
互換性イベント |
セキュリティ制限 |
対応ブラウザ |
注意点 |
onCopy |
copy |
- |
ユーザーアクション必須 |
全モダンブラウザ |
|
onCut |
cut |
- |
ユーザーアクション必須 |
全モダンブラウザ |
|
onPaste |
paste |
- |
権限確認必要 |
Chrome 66+, Firefox 125+ |
非同期API推奨 |
イベント |
イベント名 |
互換性イベント |
IE11 |
Edge |
モダンブラウザ |
注意点 |
onDragStart |
dragstart |
- |
✅ |
✅ |
✅ |
|
onDragOver |
dragover |
- |
✅ |
✅ |
✅ |
preventDefault必須 |
onDrop |
drop |
- |
✅ |
✅ |
✅ |
preventDefault必須 |
onDragEnd |
dragend |
- |
✅ |
✅ |
✅ |
|
// React 16以前
document.addEventListener('click', reactEventHandler);
// React 17以降
rootElement.addEventListener('click', reactEventHandler);
// ブラウザ間の差異をReactが吸収
const syntheticEvent = {
// 標準化されたプロパティ
type: 'click',
target: element,
currentTarget: element,
preventDefault: () => {},
stopPropagation: () => {},
// ブラウザ間で統一された値
clientX: normalizedX,
clientY: normalizedY,
key: normalizedKey, // IE11でも標準的な値
// オリジナルイベントへのアクセス
nativeEvent: originalBrowserEvent
};
// ✅ 推奨:React SyntheticEvent を使用
const handleKeyDown = (event) => {
if (event.key === 'Enter') { /* 処理 */ }
};
// ❌ 非推奨:ブラウザ固有プロパティ
const handleKeyDown = (event) => {
if (event.keyCode === 13) { /* 処理 */ }
};
// タッチイベント対応の確認
const isTouchDevice = 'ontouchstart' in window;
// ポインターイベント対応の確認
const hasPointerEvents = 'onpointerdown' in window;
function UniversalButton({ onClick, children }) {
return (
<button
onClick={onClick}
onTouchStart={isTouchDevice ? handleTouchStart : undefined}
onPointerDown={hasPointerEvents ? handlePointerDown : undefined}
>
{children}
</button>
);
}
-
React SyntheticEvent: ブラウザ間の差異を自動で正規化
-
互換性: モダンブラウザでは問題なし、IE11は制限あり
-
推奨: React標準のイベントハンドラーを使用
-
注意: 特殊なケース(clipboard, touch等)では追加考慮が必要