AI時代のReact入門 第10回:イベントハンドリング

「クリックイベントの処理は分かったけど、フォームの送信はどうすればいい?」 「イベントって結局なんなの?あのeventって何?」

Reactでイベント処理を書いていると、こんな疑問を持ったことはありませんか?実は、ReactのイベントハンドリングはHTML時代のaddEventListenerとは少し違った考え方で設計されています。

今回は、Reactでのイベントハンドリングについて、基礎から実践的な使い方まで、具体的なコード例を交えながら解説していきます。この記事を読めば、様々なユーザー操作を適切に処理できるようになり、インタラクティブなUIの実装が自在にできるようになるはずです。

Reactのイベントハンドリング:ユーザー操作の処理を極める

「クリックイベントの処理は分かったけど、フォームの送信はどうすればいい?」 「イベントって結局なんなの?あのeventって何?」

Reactでイベント処理を書いていると、こんな疑問を持ったことはありませんか?実は、ReactのイベントハンドリングはHTML時代のaddEventListenerとは少し違った考え方で設計されています。

今回は、Reactでのイベントハンドリングについて、基礎から実践的な使い方まで、具体的なコード例を交えながら解説していきます。この記事を読めば、様々なユーザー操作を適切に処理できるようになり、インタラクティブなUIの実装が自在にできるようになるはずです。

イベントとは何か

イベントとは、ユーザーの操作(クリック、入力、スクロールなど)や、システムの状態変化(ロード完了、エラー発生など)を検知する仕組みです。

HTMLでは以下のように書いていました:

HTML
<button onclick="handleClick()">クリック</button>

Reactでは以下のように書きます:

jsx
<button onClick={handleClick}>クリック</button>

主な違いは:

  1. キャメルケースを使う(onclickonClick
  2. 関数をJSX内で直接渡す
  3. イベントが合成イベント(SyntheticEvent)としてラップされる

基本的なイベントハンドリング

クリックイベントの処理

jsx
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;

フォームイベントの処理

jsx
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. 適切な命名

jsx
// ❌ 曖昧な命名
const handle = () => {};

// ✅ 目的が明確な命名
const handleSubmit = () => {};
const handleInputChange = () => {};
const handleMenuClick = () => {};

2. イベントオブジェクトの型付け

jsx
// ❌ 型のない実装
const handleChange = (e) => {
  setValue(e.target.value);
};

// ✅ イベントの型を明示
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setInput(e.target.value);
};

3. パフォーマンスの最適化

jsx
// ❌ レンダリングのたびに新しい関数が作られる
<button onClick={() => handleClick(id)}>

// ✅ useCallbackで関数を最適化
const handleClick = useCallback((id) => {
  // 処理
}, []);

よくある間違いと解決策

1. イベント伝播の制御

jsx
// ❌ イベント伝播を考慮していない
const handleClick = () => {
  // 処理
};

// ✅ イベント伝播を制御
const handleClick = (e) => {
  e.stopPropagation();  // 親要素へのイベント伝播を止める
  // 処理
};

2. フォームのデフォルト動作

jsx
// ❌ デフォルト動作を止め忘れる
const handleSubmit = () => {
  // 処理
};

// ✅ デフォルト動作を制御
const handleSubmit = (e) => {
  e.preventDefault();  // ページのリロードを防ぐ
  // 処理
};

3. thisの束縛

jsx
// ❌ thisの束縛が必要な書き方
class Component extends React.Component {
  handleClick() {
    this.setState({});  // thisがundefinedになる
  }
}

// ✅ アロー関数を使用
class Component extends React.Component {
  handleClick = () => {
    this.setState({});  // thisが正しく束縛される
  }
}

まとめ

Reactのイベントハンドリングについて学びました:

  • イベントハンドラはキャメルケースで記述
  • イベントオブジェクトは合成イベントとしてラップされる
  • フォーム処理ではpreventDefault()を忘れずに
  • パフォーマンスを考慮した実装を心がける

次回は「useEffect基礎」について学んでいきます。イベントハンドリングと組み合わせて、より高度な副作用の処理方法を解説していきます。