イベントハンドリング
この章では、Reactでのイベント処理について学びます。
この章で学ぶこと
- イベントハンドラーの基本的な書き方
- TypeScriptでのイベント型の指定
- イベントの伝播と制御
- イベントハンドラーの命名規則
この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。
イベントハンドラーの基本
Reactでは、イベントハンドラーをキャメルケースで指定します。※06章で学んだように、関数をPropsとして渡すこともできます。
function Button() {
const handleClick = () => {
console.log('クリックされました')
}
return <button onClick={handleClick}>クリック</button>
}
onClick={handleClick}は関数の参照を渡しています。onClick={handleClick()}と書くと、レンダリング時に即座に実行されてしまうので注意してください。
// OK: クリック時に実行される
<button onClick={handleClick}>
// NG: レンダリング時に即座に実行される
<button onClick={handleClick()}>
インラインでの記述
簡単な処理はインラインで記述できます。
function Button() {
return (
<button onClick={() => console.log('クリック')}>
クリック
</button>
)
}
イベントオブジェクト
イベントハンドラーは、イベントオブジェクトを引数として受け取ります。
function Input() {
// React.ChangeEvent<HTMLInputElement> はinput要素の変更イベントの型
// <HTMLInputElement> はイベントが発生した要素の型を指定
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// event.target で入力欄の要素にアクセス
// event.target.value で入力された値を取得
console.log(event.target.value)
}
return <input onChange={handleChange} />
}
SyntheticEventとは?
Reactのイベントオブジェクトは「SyntheticEvent(合成イベント)」と呼ばれます。これはブラウザ間の差異を吸収し、一貫したAPIを提供するためのラッパーです。
// React.ChangeEvent は SyntheticEvent の一種
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// e.nativeEvent で元のブラウザイベントにアクセス可能
console.log(e.nativeEvent)
}
ほとんどの場合、SyntheticEventのAPIだけで十分です。
主要なイベント型
| イベント | 型 | 発生タイミング |
|---|---|---|
| onClick | React.MouseEvent | クリック時 |
| onChange | React.ChangeEvent | 値変更時 |
| onSubmit | React.FormEvent | フォーム送信時 |
| onKeyDown | React.KeyboardEvent | キー押下時 |
| onFocus | React.FocusEvent | フォーカス時 |
| onBlur | React.FocusEvent | フォーカス解除時 |
クリックイベント
function ClickExample() {
// React.MouseEvent<HTMLButtonElement> はボタンのマウスイベントの型
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// clientX, clientY でクリック位置(ブラウザ表示領域の座標)を取得
console.log('クリック位置:', event.clientX, event.clientY)
}
return <button onClick={handleClick}>クリック</button>
}
試してみよう: クリックカウンター
ボタンをクリックするたびにカウントが増え、コンソールに「○回目のクリック」と表示されるコンポーネントを作ってみましょう。
ヒント
useState(0)でカウントを管理handleClick内でsetCountとconsole.logを呼び出すcountはそのレンダー時点の値で固定されているため、新しい値を変数に格納してから使う
回答と解説
import { useState } from 'react'
import './App.css'
function ClickCounter() {
const [count, setCount] = useState(0)
const handleClick = () => {
// count はこのレンダー時点の値で固定されているため、更新後の値を
// 使いたい場合は先に計算して変数に格納してから使う
const newCount = count + 1
setCount(newCount)
// count をそのままログに出すと、更新前の値が表示されてしまう
console.log(`${newCount}回目のクリック`)
}
return (
<div>
<p>クリック回数: {count}</p>
<button onClick={handleClick}>クリック</button>
</div>
)
}
export default function App() {
return <ClickCounter />
}
countはそのレンダー時点の値に固定されている(スナップショット)ため、コンソールログに新しい値を表示するには、count + 1を一度変数に格納してから使用しています。これにより、表示とログが常に一致します。
入力イベント
function InputExample() {
const [value, setValue] = useState('')
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// 入力欄の現在の値をStateに保存
setValue(event.target.value)
}
return (
<div>
{/* value={value} で入力欄の値をStateと同期(制御コンポーネント) */}
<input value={value} onChange={handleChange} />
<p>入力値: {value}</p>
</div>
)
}
フォーム送信イベント
function FormExample() {
const [name, setName] = useState('')
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value)
}
// フォーム送信時のハンドラ
// React.FormEvent<HTMLFormElement> はフォーム送信イベントの型
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
// ブラウザのデフォルト動作(ページ遷移)を防止
// これがないとページがリロードされてしまう
event.preventDefault()
console.log('送信:', name)
}
return (
// type="submit" のボタンをクリック、または Enter キーで onSubmit が発火
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={handleNameChange}
/>
<button type="submit">送信</button>
</form>
)
}
キーボードイベント
function KeyboardExample() {
// React.KeyboardEvent<HTMLInputElement> はキーボードイベントの型
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
// event.key で押されたキーを文字列で取得
// 'Enter', 'Escape', 'ArrowUp' など
if (event.key === 'Enter') {
console.log('Enterキーが押されました')
}
}
return <input onKeyDown={handleKeyDown} placeholder="Enterで送信" />
}
試してみよう: キーボード入力表示
入力フィールドで押されたキーをリアルタイムで表示するコンポーネントを作ってみましょう。
ヒント
onKeyDownイベントでevent.keyを取得useState('')で最後に押されたキーを管理||演算子で未入力時のデフォルト表示を設定
回答と解説
import { useState } from 'react'
import './App.css'
function KeyDisplay() {
const [lastKey, setLastKey] = useState('')
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
setLastKey(event.key)
}
return (
<div>
<input
onKeyDown={handleKeyDown}
placeholder="キーを押してください"
/>
<p>押されたキー: {lastKey || '(なし)'}</p>
</div>
)
}
export default function App() {
return <KeyDisplay />
}
event.keyでどのキーが押されたかを取得できます。Enter、Escape、ArrowUpなど、特殊キーも文字列として取得されます。||演算子で未入力時のデフォルト表示を設定しています。
イベントハンドラーに引数を渡す
追加の引数を渡したい場合は、アロー関数でラップします。
function ItemList() {
const items = ['Apple', 'Banana', 'Orange']
// 複数の引数を受け取るハンドラー
const handleClick = (item: string, index: number) => {
console.log(`${index}: ${item}がクリックされました`)
}
return (
<ul>
{items.map((item, index) => (
<li key={item}>
{/* onClick={() => handleClick(item, index)} のように
アロー関数でラップすることで、引数を渡せる
onClick={handleClick(item, index)} と書くと
レンダリング時に即座に実行されてしまうので注意 */}
<button onClick={() => handleClick(item, index)}>
{item}
</button>
</li>
))}
</ul>
)
}
イベントの伝播
イベントバブリング
イベントは子要素から親要素に伝播(バブリング)します。
function BubblingExample() {
return (
<div onClick={() => console.log('親がクリック')}>
<button onClick={() => console.log('子がクリック')}>
クリック
</button>
</div>
)
// ボタンクリック時:
// "子がクリック"
// "親がクリック"
}
伝播の停止
stopPropagation()でイベントの伝播を止められます。
function StopPropagationExample() {
return (
<div onClick={() => console.log('親がクリック')}>
<button onClick={(e) => {
e.stopPropagation()
console.log('子がクリック')
}}>
クリック
</button>
</div>
)
// ボタンクリック時: "子がクリック" のみ
}
試してみよう: イベント伝播の確認
親要素と子要素にそれぞれonClickを設定し、stopPropagation()の動作を確認してみましょう。
ヒント
- 親要素(
<div>)と子要素(<button>)それぞれにonClickを設定 - 子要素のハンドラーで
e.stopPropagation()を呼び出す - コメントアウトして動作の違いを確認
回答と解説
import './App.css'
function PropagationExample() {
const handleParentClick = () => {
console.log('親要素がクリックされました')
}
const handleChildClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log('子要素がクリックされました')
// event.stopPropagation() // コメントを外すと伝播が止まる
}
return (
<div
onClick={handleParentClick}
style={{ padding: '20px', backgroundColor: '#eee' }}
>
<p>親要素(ここをクリック)</p>
<button onClick={handleChildClick}>
子要素(ボタン)
</button>
</div>
)
}
export default function App() {
return <PropagationExample />
}
デフォルトではボタンをクリックすると「子要素がクリックされました」→「親要素がクリックされました」の順で表示されます。event.stopPropagation()のコメントを外すと、子要素のログのみが表示され、親要素への伝播が止まることが確認できます。
デフォルト動作の防止
preventDefault()でブラウザのデフォルト動作を防止できます。
function PreventDefaultExample() {
// React.MouseEvent<HTMLAnchorElement> はリンク要素のマウスイベントの型
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
// preventDefault() でブラウザのデフォルト動作(ページ遷移)を防止
// フォームの onSubmit でも同様にページリロードを防げる
event.preventDefault()
console.log('リンククリック(遷移なし)')
}
return (
<a href="https://example.com" onClick={handleClick}>
リンク
</a>
)
}
イベントハンドラーの命名規則
慣例として、イベントハンドラーはhandleで始める名前を付けます。
function Form() {
const handleSubmit = () => { /* ... */ }
const handleNameChange = () => { /* ... */ }
const handleEmailChange = () => { /* ... */ }
// Propsとして渡す場合は on で始める
return <Button onClick={handleSubmit} />
}
onとhandleの使い分け
| 場所 | プレフィックス | 例 | 意味 |
|---|---|---|---|
| Props(受け取る側) | on | onClick | 「クリック時に」 |
| 関数(定義する側) | handle | handleClick | 「クリックを処理する」 |
// 子コンポーネント: Propsは on で受け取る
function Button({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>クリック</button>
}
// 親コンポーネント: 関数は handle で定義する
function App() {
const handleClick = () => {
console.log('クリックされました')
}
return <Button onClick={handleClick} />
}
まとめ
- イベントはキャメルケースで指定(
onClick、onChange) - TypeScriptでイベントの型を指定して安全に扱う
preventDefault()でデフォルト動作を防止stopPropagation()でイベント伝播を停止- ハンドラー名は
handleで始める
次の章では、条件分岐とリスト表示について学びます。