Reactの基礎 - イベントハンドリング

2026年2月26日 (木) 06:03時点におけるWiki (トーク | 投稿記録)による版 (ページの作成:「== 概要 == Reactにおけるイベントハンドリングは、ユーザー操作 (クリック、入力、フォーム送信等) に応じてUIを動的に更新するための仕組みである。<br> HTMLのDOM APIが提供するネイティブイベントとは異なり、Reactは合成イベント (SyntheticEvent) という独自のイベントシステムを採用している。<br> <br> 合成イベントはブラウザネイティブイベントのラッ…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

Reactにおけるイベントハンドリングは、ユーザー操作 (クリック、入力、フォーム送信等) に応じてUIを動的に更新するための仕組みである。
HTMLのDOM APIが提供するネイティブイベントとは異なり、Reactは合成イベント (SyntheticEvent) という独自のイベントシステムを採用している。

合成イベントはブラウザネイティブイベントのラッパーであり、全てのブラウザで統一されたAPIを提供することでクロスブラウザ互換性を保証する。
React 16以前ではイベントプーリング (Event Pooling) という最適化が適用されていたが、React 17以降ではこの仕組みが廃止された。
そのため、e.persist() の呼び出しは不要となり、非同期処理内でもイベントオブジェクトに安全にアクセスできる。

TypeScriptを使用したReact開発では、イベントハンドラに型情報を付与することでコンパイル時のエラー検出が可能になる。
React.MouseEvent<HTMLButtonElement>React.ChangeEvent<HTMLInputElement> のように、イベントの種類と対象要素の型を組み合わせて指定するのが基本的なパターンである。

また、ReactのイベントはHTMLの属性名と対応しており、onclickonClickonchangeonChange というキャメルケースの形式で記述する。
イベントハンドラには関数そのものを渡す必要があり、onClick={handleClick} のように記述する。

onClick={handleClick()} のように関数呼び出しの形式で記述すると、レンダリング時に即座に実行されてしまうため注意が必要である。


合成イベント (SyntheticEvent)

SyntheticEventの仕組み

合成イベント (SyntheticEvent) は、Webブラウザのネイティブイベントを標準化したラッパーオブジェクトである。

Reactは異なるブラウザ間でイベントの挙動が統一されるよう、W3C仕様に準拠した形で合成イベントを実装している。
これにより、Chrome、Firefox、Safari、Edge等のWebブラウザで同一のイベント処理コードが動作する。

合成イベントは、ネイティブイベントと同等のインターフェースを持つ。
nativeEvent プロパティを通じて元のブラウザネイティブイベントオブジェクトにアクセスすることも可能である。

 function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
    // 合成イベントオブジェクトへのアクセス
    console.log(e.type);          // "click"
    console.log(e.target);        // クリックされた要素
    console.log(e.currentTarget); // ハンドラが設定されている要素
 
    // ネイティブイベントへのアクセス
    console.log(e.nativeEvent);   // MouseEvent (ブラウザネイティブ)
 }


React 16以前では、合成イベントオブジェクトはイベントプール (Event Pool) から再利用されていた。
そのため、非同期処理内でイベントオブジェクトにアクセスすると、既にプールに返却されて値がクリアされるという問題があった。

React 17以降では、イベントプーリングが廃止され、各イベントごとに新しいオブジェクトが割り当てられるようになった。

 // React 17以降 : e.persist() の呼び出しは不要
 function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // 非同期処理内でも安全にアクセスできる
    setTimeout(() => {
       console.log(e.target.value); // React 17以降は正常に動作する
    }, 1000);
 }


SyntheticEventの主要な型

下表に、Reactが提供する合成イベントの主要な型を示す。

SyntheticEventの主要な型一覧
型名 対応するイベント 主な対象要素
React.MouseEvent<T> onClick, onDoubleClick, onMouseDown, onMouseUp,
onMouseEnter, onMouseLeave, onMouseMove
button, div, a等
React.ChangeEvent<T> onChange input, select, textarea
React.FormEvent<T> onSubmit, onReset, onChange form
React.KeyboardEvent<T> onKeyDown, onKeyUp, onKeyPress input, textarea等
React.FocusEvent<T> onFocus, onBlur input, button等
React.DragEvent<T> onDrag, onDragStart, onDragEnd, onDrop,
onDragOver
div等
React.ClipboardEvent<T> onCopy, onCut, onPaste input, div等
React.TouchEvent<T> onTouchStart, onTouchMove, onTouchEnd div等
React.PointerEvent<T> onPointerDown, onPointerUp, onPointerMove div, button等
React.WheelEvent<T> onWheel div等
React.AnimationEvent<T> onAnimationStart, onAnimationEnd, onAnimationIteration div等
React.TransitionEvent<T> onTransitionEnd div等


型パラメータ T には、イベントの対象となるHTML要素の型を指定する。
例えば、React.MouseEvent<HTMLButtonElement> はボタン要素に対するマウスイベントを表す。


イベントハンドラの基本

onClickの使用方法

onClick は最もよく使用されるイベントハンドラであり、ユーザが要素を押下した時に発生する。

 import { useState } from 'react';
 
 function LikeButton() {
    const [liked, setLiked] = useState(false);
 
    // React.MouseEvent<HTMLButtonElement> 型を指定する
    function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
       console.log('クリックされた位置:', e.clientX, e.clientY);
       setLiked(!liked);
    }
 
    return (
       <button onClick={handleClick}>
          {liked ? 'いいね済み' : 'いいね'}
       </button>
    );
 }


onChangeの使用方法

onChange は、input要素、select要素、textarea要素の値が変更された時に発生する。

 import { useState } from 'react';
 
 function NameInput() {
    const [name, setName] = useState('');
 
    // React.ChangeEvent<HTMLInputElement> 型を指定する
    function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
       setName(e.target.value);
    }
 
    return (
       <div>
          <input
             type="text"
             value={name}
             onChange={handleChange}
             placeholder="名前を入力"
          />
          <p>入力値: {name}</p>
       </div>
    );
 }


select 要素の場合は、React.ChangeEvent<HTMLSelectElement>
textarea 要素の場合は、React.ChangeEvent<HTMLTextAreaElement> を使用する。

その他の主要イベント

下表に、Reactで頻繁に使用される主要なイベントハンドラを示す。

主要イベントハンドラ一覧
イベントハンドラ 発生タイミング 対応するSyntheticEvent型
onSubmit フォームが送信された時 React.FormEvent<HTMLFormElement>
onKeyDown キーが押された瞬間 React.KeyboardEvent<T>
onKeyUp キーが離された瞬間 React.KeyboardEvent<T>
onFocus 要素がフォーカスを得た時 React.FocusEvent<T>
onBlur 要素がフォーカスを失った時 React.FocusEvent<T>
onMouseEnter マウスが要素に入った時 (バブリングなし) React.MouseEvent<T>
onMouseLeave マウスが要素から出た時 (バブリングなし) React.MouseEvent<T>
onDoubleClick 要素がダブルクリックされた時 React.MouseEvent<T>
onScroll 要素がスクロールされた時 React.UIEvent<T>
onDrop ドラッグされた要素がドロップされた時 React.DragEvent<T>
onPaste テキストが貼り付けられた時 React.ClipboardEvent<T>



イベントハンドラの記述パターン

インライン関数

インライン関数は、JSX内にアロー関数を直接記述するパターンである。

簡潔な処理に適しており、ソースコードが短く可読性が高い。
ただし、コンポーネントが再レンダリングされるたびに新しい関数オブジェクトが生成されるため、パフォーマンスが重要な場面では注意が必要である。

 function Counter() {
    const [count, setCount] = useState(0);
 
    return (
       <div>
          {/* インライン関数: シンプルな処理に適する */}
          <button onClick={() => setCount(count + 1)}>
             増加
          </button>
          <button onClick={() => setCount(0)}>
             リセット
          </button>
          <p>カウント: {count}</p>
       </div>
    );
 }


別関数として定義

コンポーネント内に関数を定義し、ハンドラ名を属性値として渡すパターンである。

処理が複数行にわたる場合や、同じ関数を複数の要素で共有する場合に適している。
関数名に handle プレフィックスを付ける命名規則が一般的である。

 function SignupForm() {
    const [email, setEmail] = useState('');
 
    // function宣言による定義
    function handleEmailChange(e: React.ChangeEvent<HTMLInputElement>) {
       const value = e.target.value;
       // 複数行の処理が記述しやすい
       setEmail(value.toLowerCase().trim());
    }
 
    function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
       e.preventDefault();
       console.log('送信:', email);
    }
 
    return (
       <form onSubmit={handleSubmit}>
          {/* 関数名を渡す (呼び出しではない) */}
          <input
             type="email"
             value={email}
             onChange={handleEmailChange}
          />
          <button type="submit">登録</button>
       </form>
    );
 }


アロー関数による定義

const handleClick = () => {} の形式でコンポーネント内に関数を定義するパターンである。

function宣言と機能的には同等であるが、アロー関数では this のバインディングが発生しないため、クラスコンポーネントにおいて有用である。
関数コンポーネントでは、どちらの記法を使用しても同等に機能する。

 function DeleteButton({ id }: { id: string }) {
    // アロー関数による定義
    const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
       e.stopPropagation();
       console.log('削除:', id);
    };
 
    return (
       <button onClick={handleClick} className="btn-danger">
          削除
       </button>
    );
 }


パフォーマンスが重要な場面では、useCallback フックを使用して関数参照を安定化させることができる。

 import { useState, useCallback } from 'react';
 
 function OptimizedList() {
    const [items, setItems] = useState<string[]>([]);
 
    // useCallbackで関数をメモ化する
    const handleAddItem = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
       setItems(prev => [...prev, `アイテム${prev.length + 1}`]);
    }, []); // 依存配列が空のため、初回レンダリング時の関数参照を保持する
 
    return (
       <div>
          <button onClick={handleAddItem}>追加</button>
          <ul>
             {items.map((item, index) => (
                <li key={index}>{item}</li>
             ))}
          </ul>
       </div>
    );
 }



イベントハンドラへの引数の渡し方

アロー関数でラップする方法

イベントハンドラに引数を渡す簡単な方法は、JSX内でアロー関数を使用してラップすることである。

 interface Item {
    id: string;
    name: string;
 }
 
 interface ItemListProps {
    items: Item[];
    onDelete: (id: string) => void;
 }
 
 function ItemList({ items, onDelete }: ItemListProps) {
    // アロー関数でラップして引数を渡す
    return (
       <ul>
          {items.map(item => (
             <li key={item.id}>
                {item.name}
                {/* onClick={() => onDelete(item.id) でidを渡す */}
                <button onClick={() => onDelete(item.id)}>
                   削除
                </button>
             </li>
          ))}
       </ul>
    );
 }


イベントオブジェクトも合わせて渡す場合は、アロー関数の引数にイベントオブジェクトを受け取る。

 function handleDelete(id: string, e: React.MouseEvent<HTMLButtonElement>) {
    e.stopPropagation();
    console.log('削除:', id);
 }
 
 // アロー関数でラップしてidとイベントオブジェクトを渡す
 <button onClick={(e) => handleDelete(item.id, e)}>削除</button>


カリー化 (関数を返す関数)

カリー化とは、引数を受け取って新しい関数を返す手法である。
イベントハンドラにカリー化を適用すると、JSX内のコードをよりシンプルに記述できる。

 function TaskList() {
    const [tasks, setTasks] = useState<string[]>(['タスク1', 'タスク2']);
 
    // カリー化 : idを受け取って、イベントハンドラを返す関数
    const handleDelete = (index: number) => () => {
       setTasks(prev => prev.filter((_, i) => i !== index));
    };
 
    return (
       <ul>
          {tasks.map((task, index) => (
             <li key={index}>
                {task}
                {/* handleDelete(index) がイベントハンドラを返す */}
                <button onClick={handleDelete(index)}>
                   削除
                </button>
             </li>
          ))}
       </ul>
    );
 }


下表に、2つのアプローチを比較する。

引数渡しのアプローチ比較
アプローチ 記述例 メリット デメリット
アロー関数でラップ onClick={() => handleClick(id)} 直感的でわかりやすい 毎回新しい関数が生成される
カリー化 onClick={handleClick(id)} JSXがシンプルになる 関数の意図が理解しにくい場合がある



preventDefaultとstopPropagation

preventDefault

e.preventDefault() は、Webブラウザのデフォルト動作を抑止するメソッドである。

主なユースケースとして、フォームの送信時にページリロードを防ぐ場合と、リンクのナビゲーションを抑止する場合がある。

  • フォーム送信のデフォルト動作を抑止する場合
     function LoginForm() {
        const [email, setEmail] = useState('');
        const [password, setPassword] = useState('');
     
        function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
           // デフォルトのページリロードを抑止する
           e.preventDefault();
     
           // カスタムの送信処理を実行する
           console.log('ログイン:', { email, password });
        }
     
        return (
           <form onSubmit={handleSubmit}>
              <input
                 type="email"
                 value={email}
                 onChange={e => setEmail(e.target.value)}
              />
              <input
                 type="password"
                 value={password}
                 onChange={e => setPassword(e.target.value)}
              />
              <button type="submit">ログイン</button>
           </form>
        );
     }
    

  • リンクのデフォルトナビゲーションを抑止する場合
     function CustomLink({ href, children }: { href: string; children: React.ReactNode }) {
        function handleClick(e: React.MouseEvent<HTMLAnchorElement>) {
           // デフォルトのページ遷移を抑止する
           e.preventDefault();
    
           // カスタムのナビゲーション処理を実行する
           console.log('カスタムナビゲーション:', href);
        }
     
        return (
           <a href={href} onClick={handleClick}>
              {children}
           </a>
        );
     }
    


stopPropagation

e.stopPropagation() は、イベントが親要素に向けて伝播 (バブリング) するのを停止するメソッドである。

ネストした要素でイベントを処理する際、親要素のハンドラが意図せず実行されることを防ぐために使用する。

 function Modal({ onClose, children }: { onClose: () => void; children: React.ReactNode }) {
    function handleOverlayClick(e: React.MouseEvent<HTMLDivElement>) {
       // オーバーレイのクリックでモーダルを閉じる
       onClose();
    }
 
    function handleContentClick(e: React.MouseEvent<HTMLDivElement>) {
       // コンテンツ内のクリックが親 (オーバーレイ) に伝播しないようにする
       e.stopPropagation();
    }
 
    return (
       <div className="overlay" onClick={handleOverlayClick}>
          <div className="modal-content" onClick={handleContentClick}>
             {children}
          </div>
       </div>
    );
 }


イベントの伝播 (バブリングとキャプチャ)

DOMイベントの伝播は以下の3つのフェーズで構成される。

イベント伝播フェーズの種類
フェーズ 内容
キャプチャフェーズ イベントがルート要素から対象要素に向かって伝播するフェーズ
onClickCapture 等のキャプチャフェーズハンドラが実行される。
ターゲットフェーズ イベントが対象要素に到達するフェーズ
対象要素に設定されたハンドラが実行される。
バブリングフェーズ イベントが対象要素から親要素に向かって伝播するフェーズ
onClick 等の通常のイベントハンドラが実行される。


Reactでは、onClickCapture のように Capture サフィックスを付けることで、キャプチャフェーズのハンドラを登録できる。

 function EventPropagationExample() {
    return (
       {/* キャプチャフェーズ: 最初に実行される */}
       <div onClickCapture={() => console.log('1: 外側 (キャプチャ)')}>
          {/* キャプチャフェーズ: 2番目に実行される */}
          <div onClickCapture={() => console.log('2: 内側 (キャプチャ)')}>
             {/* ターゲットフェーズ: ボタン自体のハンドラ */}
             <button
                onClick={() => console.log('3: ボタン (ターゲット)')}
             >
                クリック
             </button>
          </div>
          {/* バブリングフェーズ: 最後に実行される */}
          {/* ※ onClickCapture と onClick は同じ要素に共存できる */}
       </div>
    );
    // クリック時の実行順: 1 → 2 → 3 (バブリングは内側から外側の順)
 }


onMouseEnteronMouseLeave はバブリングが発生しないイベントであり、stopPropagation の影響を受けない。


TypeScriptでのイベント型定義

イベントハンドラの型

下表に、TypeScriptでReactのイベントハンドラを定義する時に使用する主要な型を示す。

イベントハンドラの型一覧
イベントハンドラ TypeScript型 主な対象要素
onClick, onDoubleClick React.MouseEvent<HTMLButtonElement> button
onClick, onDoubleClick React.MouseEvent<HTMLDivElement> div
onClick, onDoubleClick React.MouseEvent<HTMLAnchorElement> a
onChange React.ChangeEvent<HTMLInputElement> input
onChange React.ChangeEvent<HTMLSelectElement> select
onChange React.ChangeEvent<HTMLTextAreaElement> textarea
onSubmit React.FormEvent<HTMLFormElement> form
onKeyDown, onKeyUp React.KeyboardEvent<HTMLInputElement> input
onFocus, onBlur React.FocusEvent<HTMLInputElement> input
onDragStart, onDrop React.DragEvent<HTMLDivElement> div
onCopy, onPaste React.ClipboardEvent<HTMLInputElement> input
onTouchStart, onTouchEnd React.TouchEvent<HTMLDivElement> div
onPointerDown, onPointerUp React.PointerEvent<HTMLDivElement> div


また、イベントハンドラ関数自体の型を指定する場合は、React.EventHandler 型ファミリーを使用できる。

EventHandler型の一覧
型名 説明
React.MouseEventHandler<T> (e: React.MouseEvent<T>) => void の省略形
React.ChangeEventHandler<T> (e: React.ChangeEvent<T>) => void の省略形
React.FormEventHandler<T> (e: React.FormEvent<T>) => void の省略形
React.KeyboardEventHandler<T> (e: React.KeyboardEvent<T>) => void の省略形
React.FocusEventHandler<T> (e: React.FocusEvent<T>) => void の省略形


カスタムイベントハンドラの型定義

コンポーネントのPropsにイベントハンドラを含める場合は、interface を使用して型を定義する。

  • 基本的なコールバックパターン
     interface ButtonProps {
        label: string;
        disabled?: boolean;
        // イベントハンドラをPropsに含める
        onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
     }
     
     function Button({ label, disabled, onClick }: ButtonProps) {
        return (
           <button onClick={onClick} disabled={disabled}>
              {label}
           </button>
        );
     }
     
     // 使用例
     function App() {
        const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
           console.log('クリック座標:', e.clientX, e.clientY);
        };
     
        return (
           <Button
              label="送信"
              onClick={handleButtonClick}
           />
        );
     }
    

  • EventHandler型を使用したPropsの定義
     interface InputProps {
        value: string;
        placeholder?: string;
        // React.ChangeEventHandler<HTMLInputElement> を使用する
        onChange: React.ChangeEventHandler<HTMLInputElement>;
        onBlur?: React.FocusEventHandler<HTMLInputElement>;
        onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
     }
     
     function TextInput({ value, placeholder, onChange, onBlur, onKeyDown }: InputProps) {
        return (
           <input
              type="text"
              value={value}
              placeholder={placeholder}
              onChange={onChange}
              onBlur={onBlur}
              onKeyDown={onKeyDown}
           />
        );
     }
    

  • カスタムデータをコールバックで返すパターン
     interface SelectableItem {
        id: string;
        name: string;
     }
     
     interface SelectableListProps {
        items: SelectableItem[];
        // イベントオブジェクトではなくカスタムデータを返すコールバック
        onSelect: (item: SelectableItem) => void;
     }
     
     function SelectableList({ items, onSelect }: SelectableListProps) {
        return (
           <ul>
              {items.map(item => (
                 <li
                    key={item.id}
                    onClick={() => onSelect(item)}
                    style={{ cursor: 'pointer' }}
                 >
                    {item.name}
                 </li>
              ))}
           </ul>
        );
     }
     
     // 使用例
     function App() {
        const handleSelect = (item: SelectableItem) => {
           console.log('選択されたアイテム:', item.name);
        };
     
        return (
           <SelectableList
              items={[{ id: '1', name: 'アイテムA' }, { id: '2', name: 'アイテムB' }]}
              onSelect={handleSelect}
           />
        );
     }
    



関連情報