Reactイベントオブジェクトとは?基礎から実践まで徹底解説

「ボタンをクリックした時の情報を取得したい…」

「event.targetとevent.currentTargetの違いが分からない」

Reactでのイベント処理は、初心者がつまずきやすいポイントの1つです。

この記事では、Reactのイベントオブジェクト(SyntheticEvent)について、基礎から実践的な使い方まで分かりやすく解説していきますね。


スポンサーリンク

Reactのイベントオブジェクト基本

SyntheticEventって何?

SyntheticEvent(シンセティックイベント)は、Reactが独自に提供するイベントオブジェクトです。

ブラウザのネイティブイベントをラップ(包み込む)して、統一されたインターフェースを提供します。

これにより、ブラウザごとの挙動の違いを気にせず、同じコードで動作するんですね。

なぜSyntheticEventが必要なの?

ブラウザによって、イベント処理の仕様が微妙に異なります。

クロスブラウザ対応

Chrome、Firefox、Safari、Edgeなど、すべてのブラウザで同じように動作します。

パフォーマンス最適化

イベントプーリングという仕組みで、メモリ効率が向上していました(React 17以前)。

一貫性

統一されたAPIで、学習コストが低くなりますよ。

ネイティブイベントとの違い

基本的なプロパティやメソッドは、ネイティブイベントと同じです。

共通点:

  • preventDefault()
  • stopPropagation()
  • target、currentTarget
  • type、timeStamp

違い:

  • イベント名がキャメルケース(onClick、onChangeなど)
  • falseを返すだけではデフォルト動作を防げない

SyntheticEventを通じて、ネイティブイベントにもアクセスできますね。


基本的なイベントハンドラーの書き方

関数コンポーネントでのイベント処理

最もシンプルな例から見ていきましょう。

function Button() {
  const handleClick = (event) => {
    console.log('Button clicked!');
    console.log('Event type:', event.type);
  };

  return <button onClick={handleClick}>Click me</button>;
}

handleClick関数の第一引数として、イベントオブジェクトが自動的に渡されます。

インラインでの記述

簡単な処理なら、直接書くこともできます。

function Button() {
  return (
    <button onClick={(event) => {
      console.log('Clicked!');
    }}>
      Click me
    </button>
  );
}

ただし、複雑な処理は別の関数に切り出す方が読みやすいですね。

引数を渡したい場合

イベントオブジェクト以外のデータも渡せます。

function ItemList() {
  const handleItemClick = (itemId, event) => {
    console.log('Item ID:', itemId);
    console.log('Event:', event);
  };

  return (
    <div>
      <button onClick={(e) => handleItemClick(1, e)}>Item 1</button>
      <button onClick={(e) => handleItemClick(2, e)}>Item 2</button>
    </div>
  );
}

アロー関数で包むことで、追加の引数を渡せます。


よく使うイベントの種類

クリックイベント

最も基本的なイベントです。

function ClickExample() {
  const handleClick = (event) => {
    console.log('Clicked element:', event.target);
  };

  const handleDoubleClick = (event) => {
    console.log('Double clicked!');
  };

  return (
    <div>
      <button onClick={handleClick}>Single Click</button>
      <button onDoubleClick={handleDoubleClick}>Double Click</button>
    </div>
  );
}

onClickは1回のクリック、onDoubleClickはダブルクリックに反応しますね。

フォーム関連イベント

ユーザー入力を扱う重要なイベントです。

function FormExample() {
  const handleChange = (event) => {
    console.log('Input value:', event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault(); // フォーム送信を防ぐ
    console.log('Form submitted!');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        onChange={handleChange} 
        placeholder="Type something"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

onChangeは入力のたびに発火し、onSubmitはフォーム送信時に実行されます。

キーボードイベント

キー入力を検知するイベントです。

function KeyboardExample() {
  const handleKeyDown = (event) => {
    console.log('Key pressed:', event.key);

    if (event.key === 'Enter') {
      console.log('Enter key pressed!');
    }
  };

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

onKeyDownonKeyUponKeyPress(非推奨)が使えますよ。

マウスイベント

マウスの動きを追跡できます。

function MouseExample() {
  const handleMouseEnter = () => {
    console.log('Mouse entered!');
  };

  const handleMouseLeave = () => {
    console.log('Mouse left!');
  };

  return (
    <div 
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      style={{ padding: '20px', background: 'lightblue' }}
    >
      Hover over me
    </div>
  );
}

ホバー効果やツールチップの実装に便利ですね。


イベントオブジェクトの主要プロパティ

event.target

実際にイベントが発生した要素を指します。

function TargetExample() {
  const handleClick = (event) => {
    console.log('Clicked element:', event.target);
    console.log('Element tag:', event.target.tagName);
    console.log('Element text:', event.target.textContent);
  };

  return (
    <div onClick={handleClick}>
      <button>Button 1</button>
      <button>Button 2</button>
    </div>
  );
}

どのボタンがクリックされたか、正確に取得できます。

event.currentTarget

イベントハンドラーが設定されている要素を指します。

function CurrentTargetExample() {
  const handleClick = (event) => {
    console.log('target:', event.target.tagName);
    console.log('currentTarget:', event.currentTarget.tagName);
  };

  return (
    <div onClick={handleClick}>
      <span>Click me</span>
    </div>
  );
}

spanをクリックすると、targetSPANcurrentTargetDIVになりますね。

event.type

発生したイベントの種類を示します。

function TypeExample() {
  const handleEvent = (event) => {
    console.log('Event type:', event.type);
  };

  return (
    <button 
      onClick={handleEvent}
      onMouseEnter={handleEvent}
      onFocus={handleEvent}
    >
      Interact with me
    </button>
  );
}

1つのハンドラーで複数のイベントを処理する際に便利です。

event.nativeEvent

元のブラウザイベントにアクセスできます。

function NativeEventExample() {
  const handleClick = (event) => {
    console.log('Synthetic event:', event);
    console.log('Native event:', event.nativeEvent);
  };

  return <button onClick={handleClick}>Click me</button>;
}

特殊な処理が必要な場合のみ使用しましょう。


重要なメソッド

preventDefault()

要素のデフォルト動作を防ぎます。

function PreventDefaultExample() {
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submission prevented!');
    // ここで独自の処理を実行
  };

  const handleLinkClick = (event) => {
    event.preventDefault();
    console.log('Link navigation prevented!');
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" />
        <button type="submit">Submit</button>
      </form>

      <a href="https://example.com" onClick={handleLinkClick}>
        Click me (won't navigate)
      </a>
    </div>
  );
}

フォーム送信やリンク遷移を制御できますね。

stopPropagation()

イベントの伝播(バブリング)を止めます。

function StopPropagationExample() {
  const handleParentClick = () => {
    console.log('Parent clicked!');
  };

  const handleChildClick = (event) => {
    event.stopPropagation();
    console.log('Child clicked! (parent will not be triggered)');
  };

  return (
    <div onClick={handleParentClick} style={{ padding: '20px', background: 'lightblue' }}>
      Parent
      <button onClick={handleChildClick}>Child</button>
    </div>
  );
}

子要素のイベントが親に伝わらないようにできます。


フォーム入力の扱い方

制御されたコンポーネント

Reactの状態で入力値を管理する方法です。

import { useState } from 'react';

function ControlledInput() {
  const [text, setText] = useState('');

  const handleChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input 
        type="text" 
        value={text} 
        onChange={handleChange}
      />
      <p>You typed: {text}</p>
    </div>
  );
}

入力値が常にReactの状態と同期されます。

複数の入力フィールド

1つのハンドラーで複数の入力を管理できます。

import { useState } from 'react';

function MultipleInputs() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input 
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input 
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input 
        name="age"
        value={formData.age}
        onChange={handleChange}
        placeholder="Age"
      />
    </form>
  );
}

name属性を使って、どの入力が変更されたか判別できますね。

チェックボックスとラジオボタン

特別な扱いが必要です。

import { useState } from 'react';

function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);

  const handleCheckboxChange = (event) => {
    setIsChecked(event.target.checked); // valueではなくchecked
  };

  return (
    <label>
      <input 
        type="checkbox"
        checked={isChecked}
        onChange={handleCheckboxChange}
      />
      {isChecked ? 'Checked' : 'Unchecked'}
    </label>
  );
}

チェックボックスはcheckedプロパティを使用します。


よくある間違いと解決方法

イベントハンドラーを実行してしまう

括弧を付けると、レンダリング時に実行されてしまいます。

// 間違い
<button onClick={handleClick()}>Click me</button>

// 正しい
<button onClick={handleClick}>Click me</button>

// 引数を渡す場合
<button onClick={() => handleClick(id)}>Click me</button>

関数の参照を渡すか、アロー関数で包みましょう。

thisのバインディング(クラスコンポーネント)

クラスコンポーネントでは、thisのバインディングが必要でした。

class Button extends React.Component {
  constructor(props) {
    super(props);
    // コンストラクタでバインド
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(event) {
    console.log(this.props);
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

関数コンポーネントとアロー関数を使えば、この問題は発生しませんよ。

イベントオブジェクトが消える

React 17以前では、イベントプーリングにより注意が必要でした。

// React 16以前の問題
function OldPoolingIssue() {
  const handleClick = (event) => {
    setTimeout(() => {
      // エラー:イベントオブジェクトが再利用済み
      console.log(event.target);
    }, 100);
  };

  return <button onClick={handleClick}>Click me</button>;
}

// 解決方法1:必要な値を保存
function Solution1() {
  const handleClick = (event) => {
    const target = event.target;
    setTimeout(() => {
      console.log(target); // OK
    }, 100);
  };

  return <button onClick={handleClick}>Click me</button>;
}

// 解決方法2:persist()を呼ぶ(React 16)
function Solution2() {
  const handleClick = (event) => {
    event.persist();
    setTimeout(() => {
      console.log(event.target); // OK
    }, 100);
  };

  return <button onClick={handleClick}>Click me</button>;
}

React 17以降では、イベントプーリングが廃止され、この問題は解消されています。


パフォーマンス最適化

useCallbackでメモ化

関数を再作成しないようにできます。

import { useState, useCallback } from 'react';

function OptimizedComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback((event) => {
    console.log('Button clicked!');
    setCount(prev => prev + 1);
  }, []); // 依存配列が空なので、関数は再作成されない

  return <button onClick={handleClick}>Count: {count}</button>;
}

子コンポーネントへの不要な再レンダリングを防げますね。

イベントデリゲーション

親要素で一括してイベントを処理する方法です。

function EventDelegation() {
  const handleListClick = (event) => {
    if (event.target.tagName === 'LI') {
      console.log('Item clicked:', event.target.textContent);
    }
  };

  return (
    <ul onClick={handleListClick}>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
    </ul>
  );
}

個別にハンドラーを設定するより、メモリ効率が良くなります。


TypeScriptでの型定義

イベントハンドラーの型

適切な型を指定することで、安全性が向上します。

import React from 'react';

function TypedComponent() {
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    console.log(event.currentTarget);
  };

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.value);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
  };

  return (
    <div>
      <button onClick={handleClick}>Click</button>
      <input onChange={handleChange} />
      <form onSubmit={handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

要素の型を指定することで、自動補完も効きますよ。

よく使う型一覧

主要なイベント型をまとめました。

// マウスイベント
React.MouseEvent<HTMLElement>

// キーボードイベント
React.KeyboardEvent<HTMLElement>

// フォームイベント
React.FormEvent<HTMLFormElement>
React.ChangeEvent<HTMLInputElement>
React.ChangeEvent<HTMLTextAreaElement>
React.ChangeEvent<HTMLSelectElement>

// フォーカスイベント
React.FocusEvent<HTMLElement>

// クリップボードイベント
React.ClipboardEvent<HTMLElement>

// ドラッグイベント
React.DragEvent<HTMLElement>

状況に応じて使い分けましょう。


カスタムイベントの作成

子から親へのデータ送信

コールバック関数を使った一般的なパターンです。

function Parent() {
  const handleChildEvent = (data) => {
    console.log('Received from child:', data);
  };

  return <Child onCustomEvent={handleChildEvent} />;
}

function Child({ onCustomEvent }) {
  const handleClick = () => {
    onCustomEvent({ message: 'Hello from child!', timestamp: Date.now() });
  };

  return <button onClick={handleClick}>Send to Parent</button>;
}

カスタムデータを親コンポーネントに渡せますね。

複数のイベントハンドラー

1つの要素に複数のイベントを設定できます。

function MultipleHandlers() {
  const handleClick = () => console.log('Clicked!');
  const handleMouseEnter = () => console.log('Mouse entered!');
  const handleMouseLeave = () => console.log('Mouse left!');

  return (
    <button
      onClick={handleClick}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      Hover and Click
    </button>
  );
}

実践的なパターン

debounce(デバウンス)

連続したイベントを制御します。

import { useState, useCallback } from 'react';

function SearchInput() {
  const [searchTerm, setSearchTerm] = useState('');

  const debounce = (func, delay) => {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => func(...args), delay);
    };
  };

  const performSearch = useCallback(
    debounce((value) => {
      console.log('Searching for:', value);
      // API呼び出しなど
    }, 500),
    []
  );

  const handleChange = (event) => {
    const value = event.target.value;
    setSearchTerm(value);
    performSearch(value);
  };

  return (
    <input 
      type="text"
      value={searchTerm}
      onChange={handleChange}
      placeholder="Search..."
    />
  );
}

入力のたびにAPIを呼ぶのではなく、入力が止まってから実行できますね。

フォームバリデーション

リアルタイムでバリデーションを実行します。

import { useState } from 'react';

function ValidationForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  const validateEmail = (value) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      setError('Invalid email format');
    } else {
      setError('');
    }
  };

  const handleChange = (event) => {
    const value = event.target.value;
    setEmail(value);
    validateEmail(value);
  };

  return (
    <div>
      <input 
        type="email"
        value={email}
        onChange={handleChange}
        placeholder="Enter email"
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

ユーザーにすぐにフィードバックを返せます。


まとめ:Reactイベントオブジェクトをマスターしよう

Reactのイベントオブジェクトを理解することで、インタラクティブなUIを作れます。

基本的な使い方から、パフォーマンス最適化まで押さえておきましょう。

この記事の重要ポイント:

  • SyntheticEventはブラウザ間の違いを吸収する
  • event.targetとevent.currentTargetの違いを理解する
  • preventDefault()でデフォルト動作を防ぐ
  • stopPropagation()でイベント伝播を制御
  • 制御されたコンポーネントで入力を管理
  • TypeScriptで型安全性を確保
  • useCallbackでパフォーマンスを最適化
  • debounceで連続イベントを制御
  • React 17以降はイベントプーリングが廃止された

まずは簡単なボタンのクリックイベントから始めてみましょう。

実際にコードを書きながら学ぶことで、イベント処理が自然と身についていきますよ。

コメント

タイトルとURLをコピーしました