React: イベント処理
最終更新:2026-10-26
イベント処理は、React アプリの神経終末のようなものです。ユーザーがボタンをクリックしたり、テキストを入力したり、フォームを送信したりすると、これらの操作はイベントを通じて処理されます。React のイベントシステムは、ネイティブの DOM イベントよりもスマートで、統一性があります。
1. 学習内容
- Reactの複合イベントとネイティブイベントの違い 一般的なイベント:onClick / onChange / onSubmit / onKeyDown
- イベントオブジェクトの処理
- イベントハンドラ関数へのパラメータの渡し方
- デフォルトの動作とイベントのバブリングを防ぐ
2. フォームの操作に関する話
(1) 課題:フォーム送信における3つの落とし穴
ボブはユーザー登録フォームを作成しており、ネイティブのJavaScriptでイベントハンドラを記述しています:
JAVASCRIPT
// Native JS:Various compatibility issues and details need to be handled manually
// 1. Get DOM Element
const form = document.getElementById('register-form')
const nameInput = document.getElementById('name')
const submitBtn = document.getElementById('submit-btn')
// 2. Event Binding
form.addEventListener('submit', function(event) {
event.preventDefault() // Prevent the page from refreshing
// 3. Manually Retrieving Form Data
const formData = new FormData(form)
const data = Object.fromEntries(formData)
// 4. Submit Data
fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data)
})
})
// 5. Real-Time Input Validation
nameInput.addEventListener('input', function(event) {
if (event.target.value.length < 2) {
showError('Name (at least) 2 characters')
} else {
hideError()
}
})
// 6. Prevent duplicate submissions when the Submit button is clicked
let isSubmitting = false
submitBtn.addEventListener('click', function() {
if (isSubmitting) return
isSubmitting = true
submitBtn.disabled = true
submitBtn.textContent = 'Submitting......'
})
ボブの問題:
- コードの分散:イベントのバインディング、データ処理、UIの更新が、さまざまな場所に分散している
- メモリリーク:イベントリスナーの解除を忘れる
- 互換性の問題:ブラウザごとにイベントAPIに違いがあります
(2) Reactにおけるイベント処理
▶ 試してみようメリット:コード行数が30行から15行に削減され、DOMの操作が不要となり、イベントの解除に関する問題やブラウザ間の互換性の問題も発生しなくなりました。
3. Reactの複合イベント
ReactのイベントはネイティブなDOMイベントではなく、SyntheticEventsです。Reactはトップレベルでこれらを監視し、クロスブラウザ対応のイベントシステムをシミュレートしています。
| 特集 | ネイティブDOMイベント | Reactの複合イベント |
|---|---|---|
| バインディング方法 | addEventListener / onclick |
JSXに直接記述 |
| ブラウザの互換性 | 差異の手動処理が必要 | 自動標準化 |
| メモリ管理 | リスナーは手動で削除する必要がある | 自動クリーンアップ |
| イベントオブジェクト | ネイティブイベント | SyntheticEvent |
| 気泡の発生を防ぐ | event.stopPropagation() |
同一(標準化) |
| ブロックのデフォルト | event.preventDefault() |
同一(標準化) |
graph TB
A[The user clicks the button] --> B[React Root Node Event Handling]
B --> C[Create a Synthetic Event Object]
C --> D[Simulated Bubbles/Capture Phase]
D --> E[Call JSX Bound to the middle handler]
style B fill:#61dafb,color:#000
style C fill:#1890ff,color:#fff
出力:
TEXT
...
"Log In" ボタンがクリックされた
ボタンにマウスホバー
マウスがボタンから離れた
4. よくあるイベントのクイックリファレンス
| イベント名 | トリガー条件 | 一般的なシナリオ | イベントオブジェクトの種類 |
|---|---|---|---|
onClick |
要素がクリックされた | ボタン、リンク、カード | MouseEvent |
onChange |
入力フィールドの内容の変更 | フォームの入力欄、ドロップダウンメニュー | ChangeEvent |
onSubmit |
フォームを送信 | ログイン/登録/検索 | FormEvent |
onFocus |
要素にフォーカスが当たる | 入力フィールドがハイライトされる | FocusEvent |
onBlur |
要素のフォーカスが外れる | 入力の検証 | FocusEvent |
onKeyDown |
キーを押してください | ショートカットキー、Enterキーで検索 | KeyboardEvent |
onKeyUp |
プレスリリース | リアルタイム検索 | KeyboardEvent |
onMouseEnter |
マウスオーバー | ホバー効果 | MouseEvent |
onMouseLeave |
マウスアウト | ホバー効果 | MouseEvent |
onScroll |
スクロール | 無限スクロール | UIEvent |
▶ サンプル:よくあるイベントの包括的な概要
▶ 実行例を展開(81 行)
出力:
TEXT
メールアドレス/パスワードのログインフォーム。メールアドレスが不正な場合 → 赤い枠線 + "The email address format is incorrect."。パスワードが 6 文字未満 → "Password must be at least 6 chars"。送信 → console.log({email, password})
5. イベントパラメータの受け渡し
(1) パラメータの直接渡し
それを矢印関数で囲み、追加のパラメータを直接渡します:
JSX
function UserList({ users, onDelete }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
{/* Arrow Functions:Incoming user.id As an additional parameter */}
<button onClick={() => onDelete(user.id)}>
Delete
</button>
</li>
))}
</ul>
)
}
// Usage
<UserList
users={users}
onDelete={(id) => console.log('Delete User:', id)}
/>
(2) イベントオブジェクトとカスタムパラメータの両方を取得する
JSX
function ColorPicker({ colors, onSelect }) {
return (
<div>
<p>Select a Color:</p>
{colors.map(color => (
<button
key={color}
onClick={(event) => {
// event:Native Synthesis Event Object
// color:Custom Parameters
onSelect(color)
console.log('Click here:', event.clientX, event.clientY)
}}
style={{
backgroundColor: color,
width: '40px',
height: '40px',
border: '2px solid #ddd',
borderRadius: '50%',
margin: '4px',
cursor: 'pointer'
}}
/>
))}
</div>
)
}
(3) イベントオブジェクトを渡す3つの方法
| メソッド | 構文 | 適用可能なシナリオ |
|---|---|---|
| 暗黙の引数渡し | onClick={handleClick} |
追加のパラメータは不要 |
| 矢印関数 | onClick={() => handleClick(id)} |
パラメータが必要 |
| 同時に通過 | onClick={(e) => handleClick(e, id)} |
イベントオブジェクトとカスタムパラメータが必要 |
6. デフォルト動作とバブリングの防止
(1) デフォルトの動作を防ぐ:preventDefault()
最も一般的な使用例は、フォームが送信された際にページが再読み込みされないようにすることです:
▶ 試してみよう(2) 気泡の発生を防ぐ:stopPropagation()
▶ 試してみよう
7. 完全な例:To-Doリスト(包括的なイベント処理)
▶ 実行例を展開(111 行)
出力:
TEXT
... ('Log In:', { email, password })
"Log In" ボタンがクリックされた
ボタンにマウスホバー
マウスがボタンから離れた
インタラクションの流れ:
- テキストボックスにタスク名を入力し、Enter キーを押すか、「追加」ボタンをクリックします → タスクがリストに追加されます
- チェックボックスにチェックを入れる → タスクを完了としてマークする(テキストに打ち消し線が入り、灰色で表示される)
- ✕ ボタンをクリック → タスクを削除する(ダブルクリックで編集機能が起動しないようにするため、
stopPropagation)- 任意のタスクをダブルクリック → 編集用のダイアログボックスが表示されます
▶ サンプル:キーボードショートカットとフォーカスの管理
▶ 実行例を展開(95 行)
出力:
TEXT
入力フィールドに自動フォーカス。Ctrl+S → "💾 Saved (Ctrl+S)"、Ctrl+Z → "↩️ Undo (Ctrl+Z)"、Escape → クリア、Enter → "✅ Confirm: [text]"。押されたキーが表示される。
出力:
TEXT
サブタイトル:"⌨️ Shortcut Keys Panel"。表示:"⌨️ Shortcut Keys Panel"。入力:Type here、Try the shortcut keys....。useEffect が副作用を管理。useRef で DOM/変数を参照
▶ サンプル:ドラッグ&ドロップによる並べ替えイベントの処理
▶ 試してみよう出力:
TEXT
ドラッグ可能な 4 つの項目(Apple、Banana、Cherry、Date)。ドラッグして並べ替え。ドラッグ中の項目は青色のハイライト枠で表示される。
出力:
TEXT
表示:"Drag to Reorder"。状態:items(setter: setItems)、dragIdx(setter: setDragIdx)
▶ サンプル:イベントリスナーをラップするためのカスタムフック
▶ 試してみよう出力:
TEXT
useEventListener フック:DOM イベントリスナーを自動的に追加/削除。アンマウント時にクリーンアップしてメモリリークを防ぐ
出力:
TEXT
状態:pos(setter: setPos)。useEffect が副作用を管理。useCallback でハンドラ関数をメモ化
▶ サンプル:フォーム送信とイベントの組み合わせ
JSX
function SearchForm({ onSearch }) {
const [query, setQuery] = useState('')
const [category, setCategory] = useState('all')
function handleSubmit(e) {
e.preventDefault()
onSearch({ query, category })
}
function handleReset() {
setQuery('')
setCategory('all')
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
<select value={category} onChange={e => setCategory(e.target.value)}
style={{ padding: 8, borderRadius: 4 }}>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button type="submit" style={{ padding: '6px 16px', cursor: 'pointer' }}>Search</button>
<button type="button" onClick={handleReset} style={{ padding: '6px 16px', cursor: 'pointer' }}>Reset</button>
</div>
</form>
)
}
出力:
TEXT
検索バー + カテゴリドロップダウン(All/Electronics/Books)。検索送信 {query, category}。リセットで両方のフィールドをクリア。
❓ よくある質問
Q なぜ単に
handleClick(id) ではなく、矢印関数 () => handleClick(id) を使うのですか?A
onClick={handleClick(id)} を直接記述すると、React はクリックされるまで待つのではなく、レンダリング中に handleClick(id) を即座に実行してしまいます。これは、(括弧付きの)関数呼び出しであり、(括弧なしの)関数参照ではないためです。正しい方法は、onClick={() => handleClick(id)}(実行を遅延させる矢印関数)または onClick={handleClick}(引数が渡されない場合に関数名を直接参照する)です。Q 1つの要素に複数の同一のイベントをバインドすることはできますか?
A 2つの
onClickを直接バインドすることはできません。解決策:① 同じハンドラー内で複数の関数onClick={() => { fn1(); fn2() }}を呼び出す;② または、その要素を複数のHOCでラップする。通常、要素に必要なイベントハンドラーは1つだけであり、その中に複数のロジックを呼び出すことができます。Q Reactのシンセティックイベントとネイティブイベントの違いは何ですか?
A Reactの
SyntheticEventは、ネイティブイベントをラップしたクロスブラウザ対応のラッパーであり、統一されたAPI(e.preventDefault()やe.stopPropagation()など)を提供し、すべてのブラウザで一貫した動作を実現します。合成イベントはイベントデリゲーションの仕組みに基づいて動作します。つまり、すべてのイベントは個々のDOM要素ではなく、ルートノードにアタッチされます。React 17以降では、イベントはdocumentではなくルートノードにデリゲートされるため、複数のバージョンのReactが共存する場合の競合を防ぐことができます。📖 まとめ
- React は SyntheticEvent を使用しており、これによりブラウザ間の互換性と自動クリーンアップが保証されます
- 一般的なイベント:onClick、onChange、onSubmit、onKeyDown、onFocus/onBlur
- 追加のパラメータを渡すには、矢印関数を使用する
onClick={() => handler(id)} event.preventDefault()デフォルトの挙動を防止する、event.stopPropagation()バブリングを防止するonClick={handler(id)}と入力しないでください(すぐに実行されてしまいます)。代わりにonClick={() => handler(id)}を使用してください。
📝 練習問題
- 基本演習(難易度 ⭐):ボタンがクリックされるたびに、表示されているカウントを1ずつ増やす
ButtonCounterコンポーネントを作成してください。onClickイベントを使用してください。 - 上級演習(難易度 ⭐⭐):「デバウンス検索」を実装した
SearchInputコンポーネントを作成してください。この検索機能は、ユーザーが入力を停止してから 500 ms 後に自動的にトリガーされます。onChangeおよびuseEffectを使用してください。 - 課題(難易度:⭐⭐⭐):ドラッグ&ドロップによる並べ替え機能を実装した
DragAndDropListコンポーネントを作成してください。onDragStart、onDragOver、およびonDropイベントを使用してください。リストの項目を新しい位置にドラッグして移動できるようにしてください。