C
React/Hooks/Lesson 11

SyntheticEvent — onChange/onSubmit の型と preventDefault を呼ぶタイミング

25分·theory
このチャプター
3/5
TypeScript

SyntheticEvent — onChange/onSubmit の型と preventDefault を呼ぶタイミング

💡 なぜ学ぶのか? — 毎日使うのに型は曖昧なまま

🎯 `onChange={(e) => ...}` の `e` が何であるかを理解せずに使うと、`e.target.value` が `any` になり、オートコンプリートもタイポ検証もすべて失われます。
💼 React のイベントオブジェクトはブラウザの `Event` のラッパー (`SyntheticEvent`) です。追加機能はありますが、シグネチャはほぼ同じです。
`onChange`、`onClick`、`onSubmit`、`onKeyDown` — それぞれ型が異なり、対象要素 (`HTMLInputElement` vs `HTMLButtonElement`) も型に含まれます。
🔗 `e.preventDefault()` を呼び出すタイミングを逃すと、フォームがリロードされたりリンクが遷移したりしてしまいます。
📈 イベント委任は React が自動的に処理します — すべてのイベントは実際には root で一括処理されます。
🏢 실무에서는
フォームを一行作るたびに直面することです。5つの型を覚えておけば一生迷いません。`useState` や `useEffect` と同様に毎日使うので、覚えてしまうのが一番です。

React イベントの重要な型 5 選

1. SyntheticEvent — ブラウザイベントのラッパー

React のイベントは標準の Event の上にクロスブラウザ互換・プーリング・委譲機能を加えたラッパーです。

2. よく使う 5 つのシグネチャ

ts
// (a) onChange — input/textarea/select
(e: React.ChangeEvent<HTMLInputElement>) => void
(e: React.ChangeEvent<HTMLTextAreaElement>) => void
(e: React.ChangeEvent<HTMLSelectElement>) => void

// (b) onSubmit — form
(e: React.FormEvent<HTMLFormElement>) => void

// (c) onClick — button/div/...
(e: React.MouseEvent<HTMLButtonElement>) => void

// (d) onKeyDown — input/textarea
(e: React.KeyboardEvent<HTMLInputElement>) => void

// (e) onFocus / onBlur
(e: React.FocusEvent<HTMLInputElement>) => void

3. target と currentTarget の違い

tsx
<button onClick={(e) => {
  e.target;        // EventTarget — 実際にクリックされた要素 (子要素の場合もある)
  e.currentTarget; // HTMLButtonElement — ハンドラが付いている要素 (button 確定)
}}>
  <span>クリック</span>
</button>

span をクリックすると e.target = spane.currentTarget = button になります。ハンドラを付けた要素の型を確実に取得するには currentTarget を使います。

4. preventDefault — 呼ぶタイミング

tsx
<form onSubmit={(e) => {
  e.preventDefault();  // ページリロード防止 — 先頭で呼ぶ
  // ... フォーム処理
}}>
tsx
<a href="/x" onClick={(e) => {
  if (modalOpen) e.preventDefault(); // 条件付き
}}>

5. チェックボックスは value ではなく checked を使う

tsx
<input type="checkbox" onChange={(e) => setAgree(e.target.checked)} />

6. イベント委譲 — React が自動で処理

<button onClick={...}> と書くだけで、実際にはルートコンテナに一度だけリスナーが付き、React が仮想ツリーを遡ってハンドラを呼び出します。この詳細を意識する必要はありません。

💻 🅰️ 型なし — e が any
// ❌ 型なし — e.target の正体が不明
import { useState } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');

  const onChange = (e) => {
    setEmail(e.target.value);     // 運が良ければ OK
    setEmail(e.target.valeu);     // ❌ タイプミスが通過
    setEmail(e.target.checked);   // input なのか checkbox なのか不明
  };

  const onSubmit = (e) => {
    // preventDefault を忘れがち
    console.log({ email });
    // → リロード → コンソールが消える
  };

  return (
    <form onSubmit={onSubmit}>
      <input onChange={onChange} />
      <button type="submit">ログイン</button>
    </form>
  );
}
💻 🅱️ 型を明示 — IDE が自動補完
// ✅ イベントタイプを明示
import { useState, type FormEvent, type ChangeEvent, type KeyboardEvent } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [remember, setRemember] = useState(false);

  // input の onChange
  const onChangeEmail = (e: ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value);
    // setEmail(e.target.valeu); ← ❌ TS が即座に赤線
  };

  // checkbox も ChangeEvent<HTMLInputElement> — ただし e.target.checked
  const onChangeRemember = (e: ChangeEvent<HTMLInputElement>) => {
    setRemember(e.target.checked);
  };

  // form の onSubmit
  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log({ email, password, remember });
  };

  // input で Enter キーのみ処理
  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') console.log('Enter');
  };

  // 共通ハンドラー — name で分岐
  const onChangeShared = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    if (name === 'email') setEmail(value);
    if (name === 'password') setPassword(value);
  };

  return (
    <form onSubmit={onSubmit}>
      <input name="email" value={email} onChange={onChangeShared} onKeyDown={onKeyDown} />
      <input name="password" type="password" value={password} onChange={onChangeShared} />
      <label>
        <input type="checkbox" checked={remember} onChange={onChangeRemember} />
        自動ログイン
      </label>
      <button type="submit">ログイン</button>
    </form>
  );
}

💡 💡 React イベント実践 5 ヶ条

1. まずこの 5 つを覚える

  • input/textarea/select の onChange: ChangeEvent<HTMLInputElement> など
  • form の onSubmit: FormEvent<HTMLFormElement>
  • button の onClick: MouseEvent<HTMLButtonElement>
  • input の onKeyDown: KeyboardEvent<HTMLInputElement>
  • focus/blur: FocusEvent<HTMLInputElement>

2. onSubmit の先頭行は必ず e.preventDefault()
忘れると GET/POST によるページ全体のリロードが発生します。

3. チェックボックスは e.target.checked を使う

tsx
onChange={(e) => setAgree(e.target.checked)}

4. e.currentTarget が安全
e.target はクリックされた子要素 (span など) になる場合があります。ハンドラを付けた要素の型は currentTarget から取得します。

5. e.stopPropagation() は React ツリー内のみ有効
外部ライブラリ (jQuery など) が document にリスナーを付けていると、React の stopPropagation では止められません。その場合は e.nativeEvent.stopImmediatePropagation() を使います。

⚡ 実際に動かしてみる — イベントオブジェクトの中身を確認

console.log でイベントオブジェクトにどのような情報が含まれているか確認しましょう。
✏️ JS 코드
📟 コンソール出力
▶ 実行ボタンを押してください
⚠️ ブラウザのサンドボックスで実行 — console.log()のみ対応、alert/fetchは不可

確認クイズ

input の onChange ハンドラの最も正確な TypeScript 型は何ですか?
💡 `React.ChangeEvent<HTMLInputElement>` が正確な型です。`e.target` が `HTMLInputElement` と推論されるため、`value`、`checked`、`name` などにオートコンプリートと型検証がすべて適用されます。`Event` はブラウザ標準であり React の SyntheticEvent ではないため不適切です。`SyntheticEvent` 単独では `target` の具体的な型を伝えることができません。
先に読むとよい概念: useReducer — Redux の基盤
次のおすすめ: カスタム Hooks
SyntheticEvent — イベントタイプ - React