「クリックイベントの処理は分かったけど、フォームの送信はどうすればいい?」 「イベントって結局なんなの?あのeventって何?」
Reactでイベント処理を書いていると、こんな疑問を持ったことはありませんか?実は、ReactのイベントハンドリングはHTML時代のaddEventListener
とは少し違った考え方で設計されています。
今回は、Reactでのイベントハンドリングについて、基礎から実践的な使い方まで、具体的なコード例を交えながら解説していきます。この記事を読めば、様々なユーザー操作を適切に処理できるようになり、インタラクティブなUIの実装が自在にできるようになるはずです。
Reactのイベントハンドリング:ユーザー操作の処理を極める
「クリックイベントの処理は分かったけど、フォームの送信はどうすればいい?」 「イベントって結局なんなの?あのeventって何?」
Reactでイベント処理を書いていると、こんな疑問を持ったことはありませんか?実は、ReactのイベントハンドリングはHTML時代のaddEventListener
とは少し違った考え方で設計されています。
今回は、Reactでのイベントハンドリングについて、基礎から実践的な使い方まで、具体的なコード例を交えながら解説していきます。この記事を読めば、様々なユーザー操作を適切に処理できるようになり、インタラクティブなUIの実装が自在にできるようになるはずです。
イベントとは何か
イベントとは、ユーザーの操作(クリック、入力、スクロールなど)や、システムの状態変化(ロード完了、エラー発生など)を検知する仕組みです。
HTMLでは以下のように書いていました:
<button onclick="handleClick()">クリック</button>
Reactでは以下のように書きます:
<button onClick={handleClick}>クリック</button>
主な違いは:
- キャメルケースを使う(
onclick
→onClick
) - 関数をJSX内で直接渡す
- イベントが合成イベント(SyntheticEvent)としてラップされる
基本的なイベントハンドリング
クリックイベントの処理
import React, { useState } from 'react';
const ClickEvents = () => {
const [clickCount, setClickCount] = useState(0);
const [position, setPosition] = useState({ x: 0, y: 0 });
// 基本的なクリックハンドラ
const handleClick = () => {
setClickCount(prev => prev + 1);
};
// パラメータ付きのクリックハンドラ
const handleClickWithParam = (message) => {
alert(message);
};
// イベントオブジェクトを使用するハンドラ
const handleMouseMove = (e) => {
setPosition({
x: e.clientX,
y: e.clientY
});
};
return (
<div className="space-y-8 p-6">
{/* 基本的なクリックイベント */}
<div className="space-y-2">
<h3 className="font-bold">基本的なクリック</h3>
<button
onClick={handleClick}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
クリック回数: {clickCount}
</button>
</div>
{/* パラメータ付きのクリックイベント */}
<div className="space-y-2">
<h3 className="font-bold">パラメータ付きクリック</h3>
<button
onClick={() => handleClickWithParam('こんにちは!')}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
>
メッセージを表示
</button>
</div>
{/* イベントオブジェクトの使用 */}
<div
className="h-32 bg-gray-100 rounded relative"
onMouseMove={handleMouseMove}
>
<h3 className="font-bold p-2">マウスの位置</h3>
<p className="p-2">
X: {position.x}, Y: {position.y}
</p>
</div>
</div>
);
};
export default ClickEvents;
フォームイベントの処理
import React, { useState } from 'react';
const FormEvents = () => {
const [formData, setFormData] = useState({
username: '',
email: '',
comment: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
// 入力値の変更を処理
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
// 入力時のバリデーション
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
// フォーム送信を処理
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitting(true);
// バリデーション
const newErrors = {};
if (!formData.username.trim()) {
newErrors.username = 'ユーザー名は必須です';
}
if (!formData.email.trim()) {
newErrors.email = 'メールアドレスは必須です';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '有効なメールアドレスを入力してください';
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
setIsSubmitting(false);
return;
}
// 実際のアプリではここでAPIリクエストを送信
setTimeout(() => {
alert('送信しました!');
setIsSubmitting(false);
setFormData({
username: '',
email: '',
comment: ''
});
}, 1000);
};
return (
<form onSubmit={handleSubmit} className="space-y-4 p-6">
<div>
<label className="block text-sm font-medium">
ユーザー名 <span className="text-red-500">*</span>
</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
className={`mt-1 block w-full rounded border shadow-sm
${errors.username ? 'border-red-500' : 'border-gray-300'}`}
/>
{errors.username && (
<p className="mt-1 text-sm text-red-500">{errors.username}</p>
)}
</div>
<div>
<label className="block text-sm font-medium">
メールアドレス <span className="text-red-500">*</span>
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`mt-1 block w-full rounded border shadow-sm
${errors.email ? 'border-red-500' : 'border-gray-300'}`}
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">{errors.email}</p>
)}
</div>
<div>
<label className="block text-sm font-medium">コメント</label>
<textarea
name="comment"
value={formData.comment}
onChange={handleChange}
rows="4"
className="mt-1 block w-full rounded border-gray-300 shadow-sm"
/>
</div>
<button
type="submit"
disabled={isSubmitting}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{isSubmitting ? '送信中...' : '送信する'}
</button>
</form>
);
};
export default FormEvents;
イベントハンドラのベストプラクティス
1. 適切な命名
// ❌ 曖昧な命名
const handle = () => {};
// ✅ 目的が明確な命名
const handleSubmit = () => {};
const handleInputChange = () => {};
const handleMenuClick = () => {};
2. イベントオブジェクトの型付け
// ❌ 型のない実装
const handleChange = (e) => {
setValue(e.target.value);
};
// ✅ イベントの型を明示
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
};
3. パフォーマンスの最適化
// ❌ レンダリングのたびに新しい関数が作られる
<button onClick={() => handleClick(id)}>
// ✅ useCallbackで関数を最適化
const handleClick = useCallback((id) => {
// 処理
}, []);
よくある間違いと解決策
1. イベント伝播の制御
// ❌ イベント伝播を考慮していない
const handleClick = () => {
// 処理
};
// ✅ イベント伝播を制御
const handleClick = (e) => {
e.stopPropagation(); // 親要素へのイベント伝播を止める
// 処理
};
2. フォームのデフォルト動作
// ❌ デフォルト動作を止め忘れる
const handleSubmit = () => {
// 処理
};
// ✅ デフォルト動作を制御
const handleSubmit = (e) => {
e.preventDefault(); // ページのリロードを防ぐ
// 処理
};
3. thisの束縛
// ❌ thisの束縛が必要な書き方
class Component extends React.Component {
handleClick() {
this.setState({}); // thisがundefinedになる
}
}
// ✅ アロー関数を使用
class Component extends React.Component {
handleClick = () => {
this.setState({}); // thisが正しく束縛される
}
}
まとめ
Reactのイベントハンドリングについて学びました:
- イベントハンドラはキャメルケースで記述
- イベントオブジェクトは合成イベントとしてラップされる
- フォーム処理では
preventDefault()
を忘れずに - パフォーマンスを考慮した実装を心がける
次回は「useEffect基礎」について学んでいきます。イベントハンドリングと組み合わせて、より高度な副作用の処理方法を解説していきます。