ブラウザイベント互換性リファレンス - nyg1971/vanillaVsReact GitHub Wiki

ブラウザイベント互換性リファレンス

概要

Reactのイベントハンドラーが実際にブラウザでどのようなイベントにマッピングされるか、およびブラウザ間の互換性について詳しく解説します。


React内部実装の変更リスク

バージョンアップによる変更可能性

React 16 → React 17での変更例

// React 16以前:documentにイベント委譲
document.addEventListener('click', reactEventHandler);

// React 17以降:rootElementにイベント委譲
rootElement.addEventListener('click', reactEventHandler);

影響範囲:

  • 手動でdocumentにイベントリスナーを追加している場合の実行順序変更
  • ポータル(Portal)との相互作用の変化
  • 複数Reactアプリが同一ページにある場合の挙動変更

React 18での変更

// Automatic Batching の導入
// イベント処理のバッチング方法が変更
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 18: 自動的にバッチング(1回の再レンダリング)
  // React 17: 2回の再レンダリング
});

Concurrent Features による将来の変更可能性

予想される変更領域

  1. イベント処理の優先度制御
// 将来的な可能性(仮想的な例)
<input 
  onChange={handleChange}
  priority="user-blocking" // 高優先度
/>
<div 
  onScroll={handleScroll}
  priority="background" // 低優先度
/>
  1. 時間分割処理(Time Slicing)との連携
// 現在:同期的なイベント処理
const handleClick = () => {
  // 重い処理が全て同期実行
  processHeavyData();
  updateUI();
};

// 将来的な可能性:中断可能な処理
const handleClick = () => {
  startTransition(() => {
    // 中断可能な処理として実行
    processHeavyData();
    updateUI();
  });
};
  1. Suspense との統合
// 将来的な可能性
const handleAsyncAction = async () => {
  // イベントハンドラー内でのSuspense制御
  return <Suspense fallback={<Loading />}>
    <AsyncComponent />
  </Suspense>;
};

対応策と推奨事項

1. Reactの公式APIを使用

// ✅ 推奨:React標準のイベントハンドラー
<button onClick={handleClick}>

// ❌ 避ける:直接的なDOM操作
useEffect(() => {
  document.addEventListener('click', handler);
}, []);

2. React DevToolsでの動作確認

  • Profilerでイベント処理のパフォーマンス監視
  • Components タブでイベントフローの確認
  • 新バージョンでの回帰テスト実施

3. 段階的な移行計画

// 機能フラグを使用した段階的移行
const useNewEventHandling = process.env.REACT_VERSION >= 18;

function Component() {
  if (useNewEventHandling) {
    return <NewEventComponent />;
  }
  return <LegacyEventComponent />;
}

4. 互換性テスト

// 自動化されたイベント処理テスト
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);

マウスイベントの互換性

onClick

ブラウザ イベント 互換性イベント 注意点
Chrome/Firefox/Safari click auxclick auxclickは中ボタン・右ボタン用
IE11 click - auxclick未対応
Edge click auxclick モダンブラウザと同様

Reactの内部処理:

// React内部的には以下のイベントを統合
- click (左クリック)
- auxclick (中・右クリック)
- キーボードでのアクティベート (Enter/Space)

onWheel

ブラウザ イベント 互換性イベント 注意点
モダンブラウザ wheel - 標準仕様
古いWebKit mousewheel - deltaのプロパティ名が異なる
古いFirefox DOMMouseScroll - 非標準、現在は非推奨
IE9+ wheel mousewheel IE9-11での挙動差異あり

Reactの互換性処理:

// Reactが内部で正規化
wheel  { deltaX, deltaY, deltaZ }
mousewheel  { wheelDeltaX, wheelDeltaY }  正規化
DOMMouseScroll  { detail }  正規化

onScroll

ブラウザ イベント 互換性イベント 注意点
全ブラウザ scroll - windowとelementで挙動差異
iOS Safari scroll - 慣性スクロール中は発火頻度が異なる
Android scroll - タッチスクロール時の発火タイミング差異

キーボードイベントの互換性

onKeyDown / onKeyUp

ブラウザ イベント 互換性イベント 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 // レガシー対応(非推奨)

onKeyPress(非推奨)

ブラウザ 状況 代替手段
全ブラウザ 非推奨化進行中 onKeyDown を使用
Firefox 部分的に削除済み -
Chrome 段階的廃止 -

フォームイベントの互換性

onChange

要素タイプ イベント 互換性イベント 発火タイミング 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    // クロスプラットフォーム対応

メディアイベントの互換性

Video/Audio Events

イベント Chrome Firefox Safari IE11 互換性イベント
play -
pause -
ended -
loadstart ⚠️ loadstart(IE9+)
canplay -

Safari特有の注意点:

// iOS Safariでは自動再生に制限
<video onPlay={...} muted playsInline /> // 必要な属性

特殊なイベントと互換性

Clipboard Events

イベント イベント名 互換性イベント セキュリティ制限 対応ブラウザ 注意点
onCopy copy - ユーザーアクション必須 全モダンブラウザ
onCut cut - ユーザーアクション必須 全モダンブラウザ
onPaste paste - 権限確認必要 Chrome 66+, Firefox 125+ 非同期API推奨

Drag and Drop Events

イベント イベント名 互換性イベント IE11 Edge モダンブラウザ 注意点
onDragStart dragstart -
onDragOver dragover - preventDefault必須
onDrop drop - preventDefault必須
onDragEnd dragend -

Reactの内部イベント処理

Event Delegation(イベント委譲)

// React 16以前
document.addEventListener('click', reactEventHandler);

// React 17以降
rootElement.addEventListener('click', reactEventHandler);

SyntheticEvent の正規化処理

// ブラウザ間の差異をReactが吸収
const syntheticEvent = {
  // 標準化されたプロパティ
  type: 'click',
  target: element,
  currentTarget: element,
  preventDefault: () => {},
  stopPropagation: () => {},
  
  // ブラウザ間で統一された値
  clientX: normalizedX,
  clientY: normalizedY,
  key: normalizedKey, // IE11でも標準的な値
  
  // オリジナルイベントへのアクセス
  nativeEvent: originalBrowserEvent
};

実装時の推奨事項

1. 互換性を考慮した実装

// ✅ 推奨:React SyntheticEvent を使用
const handleKeyDown = (event) => {
  if (event.key === 'Enter') { /* 処理 */ }
};

// ❌ 非推奨:ブラウザ固有プロパティ
const handleKeyDown = (event) => {
  if (event.keyCode === 13) { /* 処理 */ }
};

2. フィーチャー検出

// タッチイベント対応の確認
const isTouchDevice = 'ontouchstart' in window;

// ポインターイベント対応の確認
const hasPointerEvents = 'onpointerdown' in window;

3. 段階的エンハンスメント

function UniversalButton({ onClick, children }) {
  return (
    <button
      onClick={onClick}
      onTouchStart={isTouchDevice ? handleTouchStart : undefined}
      onPointerDown={hasPointerEvents ? handlePointerDown : undefined}
    >
      {children}
    </button>
  );
}

まとめ

  • React SyntheticEvent: ブラウザ間の差異を自動で正規化
  • 互換性: モダンブラウザでは問題なし、IE11は制限あり
  • 推奨: React標準のイベントハンドラーを使用
  • 注意: 特殊なケース(clipboard, touch等)では追加考慮が必要
⚠️ **GitHub.com Fallback** ⚠️