イベントハンドリング

ユーザーがボタンをクリックしたり、フォームに入力したりする操作に対して応答する仕組みがイベントハンドリングです。 React のイベント処理は HTML の標準的なイベントと似ていますが、いくつかの重要な違いがあります。 この章では、React でのイベント処理の基本から実践的なパターンまでを解説します。

React のイベント処理

React は、ブラウザのネイティブイベントをラップした SyntheticEvent(合成イベント) を使用します。 これにより、ブラウザ間の差異を吸収し、すべてのブラウザで一貫した動作を実現しています。

React のイベント処理と HTML のイベント処理の主な違いは以下の通りです。

項目 HTML React
命名規則 小文字(onclick キャメルケース(onClick
ハンドラの指定 文字列("handleClick()" 関数の参照({handleClick}
デフォルト動作の阻止 return false が使える event.preventDefault() を明示的に呼ぶ
イベントオブジェクト ネイティブ Event SyntheticEvent(ラッパー)
HTML vs React の比較
<!-- HTML -->
<button onclick="handleClick()">クリック</button>

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

React のイベント名はキャメルケースです。onclick ではなく onClickonchange ではなく onChangeonsubmit ではなく onSubmit のように記述します。

onClick イベント

最も基本的なイベントハンドラが onClick です。 ボタンやリンクなどのクリック操作に対して処理を実行します。

ClickExample.jsx
import { useState } from 'react';

function ClickExample() {
  const [message, setMessage] = useState('ボタンを押してください');

  function handleClick() {
    setMessage('ボタンが押されました!');
  }

  return (
    <div>
      <p>{message}</p>
      <button onClick={handleClick}>クリック</button>
    </div>
  );
}

export default ClickExample;

注意:onClick={handleClick()} のように括弧を付けると、 レンダリング時に関数がすぐに実行されてしまいます。 onClick={handleClick} のように関数の参照を渡してください。

イベントハンドラの書き方

イベントハンドラの定義にはいくつかのスタイルがあります。

1. 関数宣言(推奨)

関数宣言スタイル
function MyComponent() {
  function handleClick() {
    console.log('クリックされました');
  }

  return <button onClick={handleClick}>ボタン</button>;
}

2. アロー関数での定義

アロー関数スタイル
function MyComponent() {
  const handleClick = () => {
    console.log('クリックされました');
  };

  return <button onClick={handleClick}>ボタン</button>;
}

3. インライン(簡単な処理向け)

インラインスタイル
function MyComponent() {
  return (
    <button onClick={() => console.log('クリックされました')}>
      ボタン
    </button>
  );
}

命名規則: イベントハンドラの名前は handle + イベント名にするのが慣習です。 例えば、クリックなら handleClick、フォーム送信なら handleSubmit、 入力変更なら handleChange のように命名しましょう。 Props として渡す場合は on + イベント名(例: onDeleteonSave)が一般的です。

イベントオブジェクト

イベントハンドラの第一引数には、SyntheticEvent オブジェクトが渡されます。 このオブジェクトは、ブラウザのネイティブイベントと同じインターフェースを持っています。

EventObject.jsx
function EventObject() {
  function handleClick(event) {
    console.log('イベントタイプ:', event.type);       // "click"
    console.log('ターゲット要素:', event.target);     // クリックされた DOM 要素
    console.log('ターゲットのテキスト:', event.target.textContent);
  }

  return <button onClick={handleClick}>情報を表示</button>;
}

preventDefault() でデフォルト動作を阻止

リンクのページ遷移やフォームの送信など、ブラウザのデフォルト動作を阻止するには event.preventDefault() を呼び出します。

リンクのデフォルト動作を阻止
function CustomLink() {
  function handleClick(event) {
    event.preventDefault();  // ページ遷移を阻止
    console.log('リンクがクリックされましたが遷移しません');
  }

  return (
    <a href="https://example.com" onClick={handleClick}>
      このリンクは遷移しません
    </a>
  );
}

引数付きイベントハンドラ

イベントハンドラに追加の引数を渡したい場合は、アロー関数でラップします。 リスト内のアイテムを削除する場合など、ID を渡す必要がある場面でよく使われます。

引数付きハンドラ
import { useState } from 'react';

function ItemList() {
  const [items, setItems] = useState([
    { id: 1, name: 'りんご' },
    { id: 2, name: 'バナナ' },
    { id: 3, name: 'みかん' },
  ]);

  function handleDelete(id) {
    setItems(items.filter(item => item.id !== id));
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => handleDelete(item.id)}>
            削除
          </button>
        </li>
      ))}
    </ul>
  );
}

export default ItemList;

onClick={() => handleDelete(item.id)} のようにアロー関数で包むことで、 クリック時に handleDeleteitem.id を引数として受け取ります。

onChange イベント

onChange は、入力フィールドの値が変わったときに発火するイベントです。 React では、入力フィールドの値を State で管理する「制御コンポーネント(Controlled Component)」パターンが基本です。

ControlledInput.jsx
import { useState } from 'react';

function ControlledInput() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  return (
    <div>
      <label>
        名前:
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </label>

      <label>
        メール:
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </label>

      <p>入力内容: {name} ({email})</p>
    </div>
  );
}

export default ControlledInput;

複数の入力をまとめて管理

入力フィールドが多い場合は、1 つのオブジェクト State にまとめると管理しやすくなります。 name 属性を使って、どのフィールドが変更されたかを判別します。

MultiInput.jsx
import { useState } from 'react';

function MultiInput() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    message: '',
  });

  function handleChange(e) {
    const { name, value } = e.target;
    setForm(prev => ({
      ...prev,
      [name]: value,
    }));
  }

  return (
    <form>
      <input name="name"    value={form.name}    onChange={handleChange} />
      <input name="email"   value={form.email}   onChange={handleChange} />
      <textarea name="message" value={form.message} onChange={handleChange} />
    </form>
  );
}

export default MultiInput;

計算プロパティ名: [name]: value は JavaScript の計算プロパティ名(computed property name)構文です。 name 変数の値がオブジェクトのキーとして使われるため、 1 つの handleChange 関数で複数の入力フィールドを処理できます。

onSubmit イベント

フォーム送信を処理するには onSubmit イベントを使います。 フォームのデフォルト動作(ページのリロード)を防ぐため、必ず event.preventDefault() を呼び出します。

LoginForm.jsx
import { useState } from 'react';

function LoginForm() {
  const [form, setForm] = useState({ username: '', password: '' });
  const [error, setError] = useState('');

  function handleChange(e) {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
  }

  function handleSubmit(event) {
    event.preventDefault();  // ページリロードを阻止

    if (!form.username || !form.password) {
      setError('すべての項目を入力してください');
      return;
    }

    console.log('送信データ:', form);
    setError('');
    // ここで API 通信などを行う
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="error">{error}</p>}

      <label>
        ユーザー名:
        <input
          name="username"
          value={form.username}
          onChange={handleChange}
        />
      </label>

      <label>
        パスワード:
        <input
          type="password"
          name="password"
          value={form.password}
          onChange={handleChange}
        />
      </label>

      <button type="submit">ログイン</button>
    </form>
  );
}

export default LoginForm;

イベントの伝播

DOM では、イベントは子要素から親要素へと伝播(バブリング)します。 React でもこの仕組みは同じです。親要素のイベントハンドラが意図せず呼ばれてしまう場合は、 event.stopPropagation() で伝播を停止できます。

Propagation.jsx
function Propagation() {
  function handleOuterClick() {
    console.log('外側がクリックされました');
  }

  function handleInnerClick(event) {
    event.stopPropagation();  // 伝播を停止
    console.log('内側がクリックされました');
  }

  return (
    <div onClick={handleOuterClick} style={{ padding: '20px', background: '#eee' }}>
      <p>外側の領域</p>
      <button onClick={handleInnerClick}>
        内側のボタン
      </button>
    </div>
  );
}

上の例で、内側のボタンをクリックすると stopPropagation() によって 外側の handleOuterClick は呼ばれません。 もし stopPropagation() を外すと、ボタンをクリックしたときに handleInnerClickhandleOuterClick の両方が実行されます。

preventDefault vs stopPropagation: preventDefault() はブラウザのデフォルト動作(リンク遷移、フォーム送信など)を阻止します。 stopPropagation() はイベントが親要素に伝播するのを阻止します。 両者は目的が異なるため、混同しないようにしましょう。

よく使うイベント一覧

React で頻繁に使用するイベントの一覧です。

イベント名 発火タイミング 主な用途
onClick 要素がクリックされたとき ボタン、リンクの操作
onChange 入力値が変更されたとき テキスト入力、セレクトボックス、チェックボックス
onSubmit フォームが送信されたとき フォームの送信処理
onFocus 要素にフォーカスが当たったとき 入力フィールドのハイライト表示
onBlur 要素からフォーカスが外れたとき 入力値のバリデーション
onKeyDown キーが押されたとき キーボードショートカット、Enter キーでの送信
onKeyUp キーが離されたとき キー入力の検知
onMouseEnter マウスが要素に入ったとき ホバー効果、ツールチップ表示
onMouseLeave マウスが要素から出たとき ホバー効果の解除
onScroll スクロールされたとき 無限スクロール、スクロール位置の追跡
onDoubleClick ダブルクリックされたとき 編集モードの切り替え
onCopy コピー操作時 コピー操作のカスタマイズ
onDrag / onDrop ドラッグ & ドロップ操作時 ファイルアップロード、並び替え

よく使うキーボードイベントの例

EnterToSubmit.jsx
function EnterToSubmit() {
  function handleKeyDown(event) {
    if (event.key === 'Enter') {
      console.log('Enter キーが押されました');
      // 検索やメッセージ送信などを実行
    }
  }

  return (
    <input
      type="text"
      placeholder="Enter で送信"
      onKeyDown={handleKeyDown}
    />
  );
}

まとめ: React のイベントハンドリングの基本は、(1) キャメルケースのイベント名を使う、 (2) 関数の参照を渡す(括弧を付けない)、(3) 必要に応じて preventDefault()stopPropagation() を呼ぶ、の 3 点です。 ハンドラの命名は handle~、Props として渡す場合は on~ を慣習として覚えておきましょう。