イベント制御比較表 - nyg1971/vanillaVsReact GitHub Wiki

React vs Vanilla JavaScript イベント制御比較表

Reactのハイブリッド設計思想

Reactのイベント管理は2層構造になっています:

1. React管理(自動管理)

// JSX内のイベント → Reactが自動でライフサイクル管理
<button onClick={handleClick}>クリック</button>
<input onChange={handleChange} />
  • 自動登録・削除: コンポーネントのマウント/アンマウントで自動処理
  • SyntheticEvent: ブラウザ間の差異を吸収
  • 最適化: イベント委譲で効率的

2. 手動管理(開発者管理)

// React外の領域 → useEffectで手動クリーンアップ
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize); // 手動削除
}, []);
  • グローバルイベント: window, document, body等
  • サードパーティライブラリ: React外のDOM操作
  • 特殊なDOM API: Intersection Observer, Mutation Observer等

なぜこの設計?

  • 柔軟性: 完全抽象化せず、低レベルAPIも使える
  • 責任分離: React管理範囲を明確化
  • 互換性: 既存のJavaScriptライブラリとの連携

1. テキストボックスの入力検知

Vanilla JavaScript

// 方法1: keyupイベント
const textbox = document.getElementById('myInput');
textbox.addEventListener('keyup', (event) => {
  const value = event.target.value;
  console.log('入力値:', value);
});

// 方法2: inputイベント(推奨)
textbox.addEventListener('input', (event) => {
  const value = event.target.value;
  console.log('入力値:', value);
});

// 方法3: MutationObserver
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
      console.log('値変更:', mutation.target.value);
    }
  });
});
observer.observe(textbox, { attributes: true });

React

🟢 React管理 - 推奨

// 方法1: onChange(推奨・標準)
function MyComponent() {
  const [value, setValue] = useState('');
  
  const handleChange = (event) => {
    setValue(event.target.value);
    console.log('入力値:', event.target.value);
  };
  
  return <input value={value} onChange={handleChange} />;
}

// 方法2: onInput(あまり使わない)
function MyComponent() {
  const [value, setValue] = useState('');
  
  return (
    <input 
      value={value} 
      onInput={(e) => setValue(e.target.value)} 
    />
  );
}

🔴 手動管理 - 非推奨

// useRef + イベントリスナー(Reactらしくない)
function MyComponent() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    const input = inputRef.current;
    const handleInput = (e) => console.log(e.target.value);
    input.addEventListener('input', handleInput);
    return () => input.removeEventListener('input', handleInput);
  }, []);
  
  return <input ref={inputRef} />;
}

2. ボタンクリック

Vanilla JavaScript

// 方法1: addEventListener
const button = document.getElementById('myButton');
button.addEventListener('click', (event) => {
  console.log('クリックされました');
  event.preventDefault(); // デフォルト動作を防ぐ
});

// 方法2: onclickプロパティ
button.onclick = function(event) {
  console.log('クリックされました');
};

// 方法3: HTML属性(非推奨)
// <button onclick="handleClick()">クリック</button>

React

🟢 React管理 - 推奨

// 方法1: onClick(標準)
function MyComponent() {
  const handleClick = (event) => {
    console.log('クリックされました');
    event.preventDefault(); // SyntheticEvent
  };
  
  return <button onClick={handleClick}>クリック</button>;
}

// 方法2: インライン関数
function MyComponent() {
  return (
    <button onClick={() => console.log('クリック')}>
      クリック
    </button>
  );
}

// 方法3: useCallback(パフォーマンス最適化)
function MyComponent() {
  const handleClick = useCallback(() => {
    console.log('クリックされました');
  }, []);
  
  return <button onClick={handleClick}>クリック</button>;
}

🔴 手動管理 - 非推奨

// useRef + addEventListener(Reactらしくない)
function MyComponent() {
  const buttonRef = useRef(null);
  
  useEffect(() => {
    const button = buttonRef.current;
    const handleClick = () => console.log('クリック');
    button.addEventListener('click', handleClick);
    return () => button.removeEventListener('click', handleClick);
  }, []);
  
  return <button ref={buttonRef}>クリック</button>;
}

3. フォーム送信

Vanilla JavaScript

// 方法1: submit イベント
const form = document.getElementById('myForm');
form.addEventListener('submit', (event) => {
  event.preventDefault();
  const formData = new FormData(form);
  console.log('送信データ:', Object.fromEntries(formData));
});

// 方法2: ボタンクリックで手動送信
const submitButton = document.getElementById('submitBtn');
submitButton.addEventListener('click', (event) => {
  event.preventDefault();
  // 手動でフォームデータを収集
  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;
  console.log({ name, email });
});

React

🟢 React管理 - 推奨

// 方法1: onSubmit(推奨)
function MyForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('送信データ:', formData);
  };
  
  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={formData.name} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <button type="submit">送信</button>
    </form>
  );
}

🟡 React管理 + 手動管理 - 場合により有効

// useRef + FormData(非制御コンポーネント)
function MyForm() {
  const formRef = useRef(null);
  
  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(formRef.current);
    console.log('送信データ:', Object.fromEntries(formData));
  };
  
  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input name="name" />
      <input name="email" />
      <button type="submit">送信</button>
    </form>
  );
}

4. マウスイベント(ホバー、ドラッグ)

Vanilla JavaScript

// ホバー
const element = document.getElementById('myElement');
element.addEventListener('mouseenter', () => {
  console.log('マウス進入');
});
element.addEventListener('mouseleave', () => {
  console.log('マウス退出');
});

// ドラッグ
let isDragging = false;
element.addEventListener('mousedown', (e) => {
  isDragging = true;
  console.log('ドラッグ開始');
});
document.addEventListener('mousemove', (e) => {
  if (isDragging) {
    console.log('ドラッグ中:', e.clientX, e.clientY);
  }
});
document.addEventListener('mouseup', () => {
  isDragging = false;
  console.log('ドラッグ終了');
});

React

🟢 React管理 - 推奨

// ホバー
function MyComponent() {
  const [isHovered, setIsHovered] = useState(false);
  
  return (
    <div
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      style={{ backgroundColor: isHovered ? 'lightblue' : 'white' }}
    >
      ホバーしてください
    </div>
  );
}

🟡 React管理 + 手動管理 - 場合により有効

// ドラッグ(グローバルイベントが必要)
function DraggableComponent() {
  const [isDragging, setIsDragging] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  const handleMouseDown = () => setIsDragging(true); // React管理
  
  const handleMouseMove = (e) => {
    if (isDragging) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  };
  
  const handleMouseUp = () => setIsDragging(false);
  
  useEffect(() => {
    if (isDragging) {
      // グローバルイベント = 手動管理
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
      return () => {
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, [isDragging]);
  
  return (
    <div 
      onMouseDown={handleMouseDown} // React管理
      style={{ position: 'absolute', left: position.x, top: position.y }}
    >
      ドラッグできます
    </div>
  );
}

5. キーボードイベント

Vanilla JavaScript

// グローバルキーイベント
document.addEventListener('keydown', (event) => {
  if (event.key === 'Enter') {
    console.log('Enterキー押下');
  }
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault();
    console.log('Ctrl+S 保存ショートカット');
  }
});

// 特定要素のキーイベント
const input = document.getElementById('myInput');
input.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    input.blur(); // フォーカス外す
  }
});

React

🟢 React管理 - 推奨

// 要素固有のキーイベント
function MyInput() {
  const inputRef = useRef(null);
  
  const handleKeyDown = (event) => {
    if (event.key === 'Escape') {
      inputRef.current.blur();
    }
    if (event.key === 'Enter') {
      console.log('入力確定:', event.target.value);
    }
  };
  
  return <input ref={inputRef} onKeyDown={handleKeyDown} />;
}

🟡 手動管理 - グローバルイベントで有効

// グローバルキーイベント
function MyComponent() {
  useEffect(() => {
    const handleKeyDown = (event) => {
      if (event.key === 'Enter') {
        console.log('Enterキー押下');
      }
      if (event.ctrlKey && event.key === 's') {
        event.preventDefault();
        console.log('Ctrl+S 保存ショートカット');
      }
    };
    
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, []);
  
  return <div>キーイベント監視中</div>;
}

6. ページロード・アンロードイベント

Vanilla JavaScript

// ページロード
window.addEventListener('load', () => {
  console.log('ページ完全読み込み完了');
});

document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM構築完了');
});

// ページアンロード
window.addEventListener('beforeunload', (event) => {
  event.preventDefault();
  event.returnValue = ''; // 確認ダイアログ表示
});

window.addEventListener('unload', () => {
  console.log('ページ離脱');
});

React

🟢 React管理 - 推奨

// コンポーネントマウント・アンマウント
function MyComponent() {
  useEffect(() => {
    // マウント時(ページロード相当)
    console.log('コンポーネントマウント');
    
    // アンマウント時(ページアンロード相当)
    return () => {
      console.log('コンポーネントアンマウント');
    };
  }, []);
  
  return <div>コンポーネント</div>;
}

🟡 手動管理 - グローバルイベントで有効

// ページ離脱防止
function MyComponent() {
  useEffect(() => {
    const handleBeforeUnload = (event) => {
      event.preventDefault();
      event.returnValue = '';
    };
    
    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
  }, []);
  
  return <div>コンポーネント</div>;
}

まとめ:主な違い

項目 Vanilla JavaScript React
イベント登録 addEventListener JSX属性(onClick, onChange等)
イベント削除 removeEventListener useEffect のクリーンアップ
状態管理 手動でDOM更新 useState で自動再レンダリング
パフォーマンス 手動最適化が必要 Virtual DOM で自動最適化
クリーンアップ 手動で管理 useEffect で自動管理
デバッグ ブラウザDevTools React DevTools + ブラウザ

React の利点

  • 宣言的: 「どう見えるか」を記述
  • 自動管理: イベントリスナーの登録・削除
  • 統一性: 全てのイベントがSyntheticEvent
  • 最適化: 不要な更新を自動で防ぐ

Vanilla JavaScript の利点

  • 直接制御: より細かい制御が可能
  • 軽量: フレームワークの overhead なし
  • 柔軟性: 任意のDOM操作が可能

React イベントハンドラー一覧(onXXXX)

マウスイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onClick クリック click <button onClick={handleClick}> 非推奨
onDoubleClick ダブルクリック dblclick <div onDoubleClick={handleDouble}> 非推奨
onMouseDown マウスボタン押下 mousedown <div onMouseDown={handleMouseDown}> 非推奨
onMouseUp マウスボタン離す mouseup <div onMouseUp={handleMouseUp}> 非推奨
onMouseEnter マウス進入 mouseenter <div onMouseEnter={handleEnter}> 非推奨
onMouseLeave マウス退出 mouseleave <div onMouseLeave={handleLeave}> 非推奨
onMouseOver マウスオーバー(バブリング有) mouseover <div onMouseOver={handleOver}> 非推奨
onMouseOut マウスアウト(バブリング有) mouseout <div onMouseOut={handleOut}> 非推奨
onMouseMove マウス移動 mousemove <div onMouseMove={handleMove}> +React管理で有効
onContextMenu 右クリックメニュー contextmenu <div onContextMenu={handleContext}> 非推奨

キーボードイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onKeyDown キー押下 keydown <input onKeyDown={handleKeyDown}> グローバルで有効
onKeyUp キー離す keyup <input onKeyUp={handleKeyUp}> グローバルで有効
onKeyPress キー押下(非推奨) keypress <input onKeyPress={handleKeyPress}> 非推奨

フォームイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onChange 値変更 change, input <input onChange={handleChange}> 非推奨
onInput 入力イベント input <input onInput={handleInput}> 非推奨
onSubmit フォーム送信 submit <form onSubmit={handleSubmit}> 非推奨
onReset フォームリセット reset <form onReset={handleReset}> 非推奨
onFocus フォーカス取得 focus <input onFocus={handleFocus}> 非推奨
onBlur フォーカス失う blur <input onBlur={handleBlur}> 非推奨
onSelect テキスト選択 select <input onSelect={handleSelect}> 非推奨

ドラッグ&ドロップイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onDrag ドラッグ中 drag <div onDrag={handleDrag}> +React管理で有効
onDragStart ドラッグ開始 dragstart <div onDragStart={handleDragStart}> 非推奨
onDragEnd ドラッグ終了 dragend <div onDragEnd={handleDragEnd}> 非推奨
onDragEnter ドラッグ要素が進入 dragenter <div onDragEnter={handleDragEnter}> 非推奨
onDragLeave ドラッグ要素が退出 dragleave <div onDragLeave={handleDragLeave}> 非推奨
onDragOver ドラッグ要素が上を通過 dragover <div onDragOver={handleDragOver}> 非推奨
onDrop ドロップ drop <div onDrop={handleDrop}> 非推奨

クリップボードイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onCopy コピー copy <div onCopy={handleCopy}> グローバルで有効
onCut カット cut <div onCut={handleCut}> グローバルで有効
onPaste ペースト paste <input onPaste={handlePaste}> グローバルで有効

タッチイベント(モバイル)

イベント 説明 主要ブラウザイベント 使用例 手動管理
onTouchStart タッチ開始 touchstart <div onTouchStart={handleTouchStart}> 非推奨
onTouchMove タッチ移動 touchmove <div onTouchMove={handleTouchMove}> +React管理で有効
onTouchEnd タッチ終了 touchend <div onTouchEnd={handleTouchEnd}> 非推奨
onTouchCancel タッチキャンセル touchcancel <div onTouchCancel={handleTouchCancel}> 非推奨

ホイールイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onWheel マウスホイール wheel <div onWheel={handleWheel}> 非推奨
onScroll スクロール scroll <div onScroll={handleScroll}> グローバルで有効

メディアイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onPlay 再生開始 play <video onPlay={handlePlay}> 非推奨
onPause 一時停止 pause <video onPause={handlePause}> 非推奨
onEnded 再生終了 ended <video onEnded={handleEnded}> 非推奨
onLoadStart 読み込み開始 loadstart <video onLoadStart={handleLoadStart}> 非推奨
onLoad 読み込み完了 load <img onLoad={handleLoad}> 非推奨
onError エラー発生 error <img onError={handleError}> 非推奨

アニメーション・トランジションイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onAnimationStart アニメーション開始 animationstart <div onAnimationStart={handleAnimStart}> 非推奨
onAnimationEnd アニメーション終了 animationend <div onAnimationEnd={handleAnimEnd}> 非推奨
onAnimationIteration アニメーション反復 animationiteration <div onAnimationIteration={handleAnimIter}> 非推奨
onTransitionEnd トランジション終了 transitionend <div onTransitionEnd={handleTransEnd}> 非推奨

その他のイベント

イベント 説明 主要ブラウザイベント 使用例 手動管理
onResize リサイズ resize <div onResize={handleResize}> グローバルで有効
onToggle details要素の開閉 toggle <details onToggle={handleToggle}> 非推奨

手動管理の判定基準

  • 非推奨: React管理を使うべき
  • +React管理で有効: グローバルイベントと組み合わせる場合に有効
  • グローバルで有効: window/documentレベルのイベント監視で有効

実装例

// 複数イベントの組み合わせ例
function InteractiveComponent() {
  const [state, setState] = useState({
    isHovered: false,
    isDragging: false,
    value: ''
  });

  return (
    <div
      onMouseEnter={() => setState(s => ({...s, isHovered: true}))}
      onMouseLeave={() => setState(s => ({...s, isHovered: false}))}
      onDragStart={() => setState(s => ({...s, isDragging: true}))}
      onDragEnd={() => setState(s => ({...s, isDragging: false}))}
    >
      <input
        value={state.value}
        onChange={(e) => setState(s => ({...s, value: e.target.value}))}
        onFocus={() => console.log('フォーカス')}
        onBlur={() => console.log('ブラー')}
        onKeyDown={(e) => {
          if (e.key === 'Enter') {
            console.log('エンター押下');
          }
        }}
      />
    </div>
  );
}
⚠️ **GitHub.com Fallback** ⚠️