第2回
Reactイベントハンドラーの型定義:onClick・onChange・onSubmit
TypeScriptでReactのイベントハンドラーを正しく型定義する方法を解説。onClick・onChange・onSubmit・フォーム要素ごとの型からpropsでの受け渡しまで実例付きで説明します。
制作・運営:React Guide 運営者
イベントハンドラーの型の基本
ReactのイベントはブラウザのネイティブEventではなく、Reactがラップした**合成イベント(SyntheticEvent)**です。TypeScriptでは React.MouseEvent や React.ChangeEvent などの型を使います。
import { type MouseEvent, type ChangeEvent } from 'react'
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.textContent)
}
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
}onClick
// ボタンのクリック
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
console.log('クリックされた')
}
<button onClick={handleClick}>クリック</button>
// divのクリック
const handleDivClick = (e: MouseEvent<HTMLDivElement>) => {}
// イベントオブジェクトが不要な場合は省略可
<button onClick={() => setCount(c => c + 1)}>+</button>onChange:入力フォームの値を取得
const [value, setValue] = useState('')
// inputのonChange
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
// textareaのonChange
const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setText(e.target.value)
}
// selectのonChange
const handleSelect = (e: ChangeEvent<HTMLSelectElement>) => {
setSelected(e.target.value)
}<input type="text" value={value} onChange={handleChange} />
<textarea value={text} onChange={handleTextChange} />
<select value={selected} onChange={handleSelect}>
<option value="a">A</option>
<option value="b">B</option>
</select>チェックボックス:value ではなく checked を使う
const [checked, setChecked] = useState(false)
const handleCheckChange = (e: ChangeEvent<HTMLInputElement>) => {
setChecked(e.target.checked) // valueではなくchecked
}
<input type="checkbox" checked={checked} onChange={handleCheckChange} />onSubmit:フォームの送信
import { type FormEvent } from 'react'
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault() // ページリロードを防ぐ
const form = e.currentTarget
const name = new FormData(form).get('name')
if (typeof name !== 'string') return
console.log(name)
}
<form onSubmit={handleSubmit}>
<input name="name" type="text" />
<button type="submit">送信</button>
</form>onKeyDown / onKeyUp
import { type KeyboardEvent } from 'react'
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.currentTarget.form?.requestSubmit()
}
if (e.key === 'Escape') {
handleClose()
}
}
<input onKeyDown={handleKeyDown} />イベントハンドラーをpropsとして渡す
コンポーネントにイベントハンドラーを渡すとき、型を明示します。
// ❌ 引数と戻り値の契約が分からない
type ButtonProps = {
onClick: Function
}
// ✅ 正しい型定義
type ButtonProps = {
onClick: () => void
onChange?: (value: string) => void
onSubmit?: (data: FormData) => void
}
const Button = ({ onClick }: ButtonProps) => (
<button onClick={onClick}>クリック</button>
)MouseEvent と引数を組み合わせる
クリックされた要素のIDなど、追加の引数と組み合わせる場合です。
// ❌ handleDeleteがvoidを返す場合、レンダー中に削除を実行してしまう
<button onClick={handleDelete(item.id)}>削除</button>
// ✅ アロー関数でラップする
<button onClick={() => handleDelete(item.id)}>削除</button>
// または、data属性を使う
const handleDelete = (e: MouseEvent<HTMLButtonElement>) => {
const id = e.currentTarget.dataset['id']
if (id !== undefined) deleteItem(id)
}
<button onClick={handleDelete} data-id={item.id}>削除</button>カスタムイベントの型:子→親へ値を渡す
// 子コンポーネントが親に値を返すパターン
type SearchBoxProps = {
onSearch: (query: string) => void
}
const SearchBox = ({ onSearch }: SearchBoxProps) => {
const [query, setQuery] = useState('')
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
onSearch(query) // 親に値を渡す
}
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">検索</button>
</form>
)
}
// 親コンポーネント
const handleSearch = (query: string) => {
console.log('検索:', query)
}
<SearchBox onSearch={handleSearch} />よく使うイベント型の一覧
| イベント | 型 | 要素例 |
|---|---|---|
| クリック | MouseEvent<T> |
button, div, a |
| 入力値変化 | ChangeEvent<T> |
input, select, textarea |
| フォーム送信 | FormEvent<HTMLFormElement> |
form |
| キー操作 | KeyboardEvent<T> |
input, div |
| フォーカス | FocusEvent<T> |
input, button |
| ドラッグ | DragEvent<T> |
div |
| タッチ | TouchEvent<T> |
div(モバイル) |
まとめ
e.target.valueを使うときはChangeEvent<HTMLInputElement>など要素型を指定する- チェックボックスは
e.target.checked - JavaScriptでフォーム送信を処理し、通常の送信を止めたい場合は
e.preventDefault()を呼ぶ - 引数を追加したいときはアロー関数でラップする
- コンポーネントのpropsとして渡すときは
() => voidや(value: string) => voidの形で型定義する
確認してみる
入力欄でEnterを押してフォーム送信が1回になるか確認する。日本語変換中のEnterを独自送信に使う場合はisComposingも確認する。
同じテーマの教材
【解決】TypeScript × React でよくある型エラー集:原因と修正パターン
TypeScript + Reactでよく遭遇する型エラーを解説します。propsの型定義・イベントハンドラの型・useStateの型引数・children・非同期の型推論など実務頻出パターンをまとめました。
useState・useRef・useReducerの型定義:TypeScriptで正しく型をつける
TypeScriptでuseState・useRef・useReducerに型をつける方法を解説。型推論が効くケースと明示が必要なケース、useRefのnull初期値問題まで実例付きで説明します。
ReactコンポーネントのProps型定義:基本から実践パターンまで
TypeScriptでReactのprops型を正しく定義する方法を解説。必須・省略可能・デフォルト値・children・ユニオン型・discriminated unionまで、実務で使うパターンを実例付きで網羅します。