dev React

React Event Handling入門:クリック・入力・フォームイベントをハンズオンで学ぶ

React event handlingの基本を、実際にコードを書いて動かしながら学びました。この記事では、クリックイベント、入力イベント、イベントオブジェクト、デフォルト動作の防止まで、ハンズオンで確認した内容を整理していきます。

注記: 本記事に掲載しているユーザー名・PC名はダミーに差し替えています。

参照元

得られるもの

React event handlingの基本的な書き方と、TypeScriptでのイベント型定義を他言語経験者の視点で整理した記事です。

  • onClickによるクリックイベントの基本
  • onChangeによる入力イベントの処理
  • イベントオブジェクトの型定義(MouseEvent, KeyboardEvent, FocusEventなど)
  • preventDefaultによるデフォルト動作の防止
  • document.getElementByIdがReact的にアンチパターンである理由

ハンズオンでの動作確認を通じて、C/C++やC#のイベント処理との対比も交えながら理解を深められる内容になっています。

プロジェクトのフォルダ構成

src/
├── App.css
├── App.tsx
├── assets/
├── ClickExample.tsx
├── EventExample.tsx
├── FormExample.tsx
├── InputExample.tsx
├── index.css
└── main.tsx

前提条件

  • Vite + React + TypeScript のプロジェクトが作成済みであること
  • Tailwind CSSが設定済みであること

環境構築やTailwind CSSの導入がまだの方は、前回の記事を参照してください。

クリックイベントの基本

まず、最もシンプルなクリックイベントから確認しました。

src/ClickExample.tsx:

const ClickExample = () => {
  const showMessage = (message: string) => {
    alert(message)
  }

  const showTime = () => {
    const now = new Date()
    alert(`現在の時刻: ${now.toLocaleTimeString()}`)
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">様々なクリックイベント</h2>

      <div className="space-y-4">
        <button
          onClick={() => showMessage('こんにちは!')}
          className="block bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600"
        >
          挨拶メッセージ
        </button>

        <button
          onClick={showTime}
          className="block bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600"
        >
          現在時刻を表示
        </button>
      </div>
    </div>
  )
}

export default ClickExample

ポイントは onClick の書き方が2種類ある点です。引数を渡したい場合は onClick={() => showMessage('こんにちは!')} のようにアロー関数でラップし、引数なしなら onClick={showTime} のように関数名を直接指定します。

「挨拶メッセージ」ボタンを押すとalertで「こんにちは!」と表示され、「現在時刻を表示」ボタンを押すと現在の時刻が表示されました。

React event handling クリックイベントのボタン表示画面
React event handling 挨拶メッセージボタン押下時のalert表示
React event handling 現在時刻表示ボタン押下時のalert表示

入力イベントの処理

次に、テキスト入力のイベントを確認しました。

src/InputExample.tsx:

const InputExample = () => {
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log('入力された値:', event.target.value)
  }

  const handleSubmit = () => {
    const inputElement = document.getElementById('nameInput') as HTMLInputElement
    const inputValue = inputElement.value

    if (inputValue.trim() === '') {
      alert('名前を入力してください')
    } else {
      alert(`こんにちは、${inputValue}さん!`)
    }
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">入力イベントの例</h2>

      <div className="max-w-md">
        <label className="block text-sm font-medium mb-2">
          お名前を入力してください
        </label>

        <input
          id="nameInput"
          type="text"
          onChange={handleInputChange}
          placeholder="名前を入力"
          className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500"
        />

        <button
          onClick={handleSubmit}
          className="mt-4 bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600"
        >
          送信
        </button>
      </div>
    </div>
  )
}

export default InputExample

onChange イベントは、入力欄の値が変わるたびに発火します。コンソールで確認すると、1文字入力するたびに handleInputChange が呼ばれ、hhohoghoge と入力値が出力されるのがわかりました。

React event handling 入力イベントのフォーム表示画面
React event handling 名前未入力時のバリデーションalert表示
React event handling 名前入力後の送信結果alert表示
React event handling onChangeイベントでコンソールに入力値が出力される様子

イベントの型について

TypeScriptでは、イベントハンドラの引数に型を明示する必要があります。event はstringではなくイベントオブジェクトです。入力イベントの場合は React.ChangeEvent<HTMLInputElement> を指定します。

C#でいうと、WinFormsの TextChanged イベントで senderEventArgs を受け取るのと同じ感覚です。event.target が「どのHTML要素で発生したか」、.value がその要素の値を示します。

document.getElementByIdはReact的にアンチパターン

上記の handleSubmit では document.getElementById を使ってDOM要素を直接取得していますが、これはReact的にはアンチパターンです。Reactでは、DOM直接操作を避けて状態(state)で管理するのが基本的な設計思想です。

この書き方でも動作はしますが、後のChapter(useState)で学ぶ状態管理を使えば、よりReactらしい書き方に改善できます。今回は教材のイベントハンドリングの学習として、まずこの形で動作を確認しました。

名前未入力で送信すると「名前を入力してください」のバリデーションが動き、「hoge」と入力して送信すると「こんにちは、hogeさん!」と表示されることを確認しました。

イベントオブジェクトの型定義

Reactでは、イベントの種類ごとに異なる型を使い分けます。

src/EventExample.tsx:

const EventExample = () => {
  const handleMouseEvent = (event: React.MouseEvent<HTMLDivElement>) => {
    console.log('マウスの位置:', event.clientX, event.clientY)
    console.log('クリックされた要素:', event.currentTarget.tagName)
  }

  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
    console.log('押されたキー:', event.key)
    if (event.key === 'Enter') {
      alert('Enterキーが押されました!')
    }
  }

  const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.backgroundColor = 'lightyellow'
  }

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.backgroundColor = 'white'
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">イベントオブジェクトの例</h2>

      <div className="space-y-4">
        <div>
          <button
            onClick={handleMouseEvent}
            className="bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600"
          >
            クリック位置を表示(コンソールを確認)
          </button>
        </div>

        <div>
          <input
            type="text"
            onKeyDown={handleKeyPress}
            placeholder="何か入力してください(Enterキーを試してみて)"
            className="w-full max-w-md px-3 py-2 border border-gray-300 rounded-lg"
          />
        </div>

        <div>
          <input
            type="text"
            onFocus={handleFocus}
            onBlur={handleBlur}
            placeholder="フォーカス時に背景色が変わります"
            className="w-full max-w-md px-3 py-2 border border-gray-300 rounded-lg"
          />
        </div>
      </div>
    </div>
  )
}

export default EventExample

イベント型の一覧

パターンは React.○○Event<対象のHTML要素> です。C#の EventHandler<TEventArgs> でイベント種別ごとに MouseEventArgs, KeyEventArgs を使い分けるのと同じ考え方です。

イベント
onChangeReact.ChangeEvent
onClick / onMouseMoveReact.MouseEvent
onKeyDown / onKeyPressReact.KeyboardEvent
onFocus / onBlurReact.FocusEvent
onSubmitReact.SubmitEvent

<> の中はイベントが発生するHTML要素に合わせます。<HTMLInputElement>, <HTMLButtonElement>, <HTMLDivElement> など。

target と currentTarget の違い

event.target の型は EventTarget と推論されるため、tagName などのプロパティにアクセスすると型エラーになります。対処法は2つあります。

型アサーションを使う方法:

console.log('クリックされた要素:', (event.target as HTMLElement).tagName)

currentTarget を使う方法:

console.log('クリックされた要素:', event.currentTarget.tagName)

event.target は実際にクリックされた要素(子要素の可能性あり)、event.currentTarget はイベントハンドラが設定された要素自体を指します。

ボタンクリックでコンソールにマウス座標と要素名が表示され、Enterキー押下でalertが表示され、フォーカス時に背景色がlightyellowに変わることを確認しました。

React event handling イベントオブジェクトの各種イベント確認画面
React event handling マウスイベントのコンソール出力でマウス座標と要素名を表示
React event handling Enterキー押下時のalert表示
React event handling フォーカス時に背景色がlightyellowに変わる様子

FormEventの非推奨について

ハンズオン中に React.FormEvent を使ったところ、TypeScriptから「非推奨」の警告が出ました。新しいReactの型定義では React.FormEvent の代わりに React.SubmitEvent を使うよう推奨されています。

デフォルト動作の防止(preventDefault)

ブラウザにはフォーム送信時のページリロードやリンククリック時の画面遷移など、デフォルトの動作があります。preventDefault() でこれらを防止できます。

src/FormExample.tsx:

const FormExample = () => {
  const handleSubmit = (event: React.SubmitEvent) => {
    event.preventDefault()

    const formData = new FormData(event.target as HTMLFormElement)
    const name = formData.get('name')
    const email = formData.get('email')

    alert(`フォーム送信処理\n名前: ${name}\nメール: ${email}`)
  }

  const handleLinkClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
    event.preventDefault()
    alert('リンクのデフォルト動作を防ぎました')
  }

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-6">preventDefault()の例</h2>

      <div className="space-y-8">
        <div>
          <h3 className="text-lg font-semibold mb-4">フォーム送信の例</h3>
          <form onSubmit={handleSubmit} className="max-w-md space-y-4">
            <div>
              <label className="block text-sm font-medium mb-1">名前</label>
              <input
                name="name"
                type="text"
                required
                className="w-full px-3 py-2 border border-gray-300 rounded-lg"
              />
            </div>

            <div>
              <label className="block text-sm font-medium mb-1">メールアドレス</label>
              <input
                name="email"
                type="email"
                required
                className="w-full px-3 py-2 border border-gray-300 rounded-lg"
              />
            </div>

            <button
              type="submit"
              className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600"
            >
              送信(ページはリロードされません)
            </button>
          </form>
        </div>

        <div>
          <h3 className="text-lg font-semibold mb-4">リンクの例</h3>
          <a
            href="https://example.com"
            onClick={handleLinkClick}
            className="text-blue-500 underline hover:text-blue-700"
          >
            このリンクをクリックしても移動しません
          </a>
        </div>
      </div>
    </div>
  )
}

export default FormExample

フォームに名前とメールアドレスを入力して送信すると、ページがリロードされずにalertで入力内容が表示されました。リンクをクリックしても example.com には遷移せず、alertが表示されることを確認しました。

React event handling フォーム送信時にpreventDefaultでページリロードを防止した結果
React event handling リンククリック時にpreventDefaultで遷移を防止した結果

まとめ

今回は、Reactのイベントハンドリングの基本を一通りハンズオンで確認しました。

振り返ると、ReactのイベントハンドリングはC#のイベント処理と共通する部分が多いことがわかりました。イベントの種類ごとに型が異なる点は EventHandler<TEventArgs> の使い分けと同じ感覚ですし、event.targetevent.currentTarget の違いは sender の使い方に似ています。

TypeScriptでの型定義では、React.○○Event<対象のHTML要素> というパターンを覚えておけば対応できます。また、React.FormEvent が非推奨になり React.SubmitEvent を使うようになっている点は、実際にコードを書いて初めて気づけたポイントでした。

document.getElementById によるDOM直接操作はReact的にはアンチパターンであり、次のChapter(useState)で学ぶ状態管理を使うのが正しいアプローチです。

それでは、また別の記事でお会いしましょう。最後まで読んでいただきありがとうございました!

これまでの学習内容

この記事で紹介した内容の他にも学習した内容を記事にしています。是非、ご覧になってみて下さい。

React入門 Vite + React + TypeScriptで開発環境を構築する手順をハンズオン形式で解説
Vite React TypeScript setupでTodoAppの開発環境を構築する

Vite React TypeScript setup の手順を、実際のハンズオンを通じてステップバイステップで解説します。この記事は、筆者がReact学習の第一歩として環境構築を行った際の体験をもと ...

続きを見る

react jsx propsの基本を他言語経験者向けに解説する入門記事のアイキャッチ
React入門:JSX・コンポーネント・Propsを理解する【他言語経験者向け】

Reactの学習を始めて、JSXの書き方、関数コンポーネント、Propsの仕組みまで一通り手を動かしてみました。この記事では、react jsx propsの基本を他言語経験者の視点で整理していきます ...

続きを見る

react event handlingの基本をクリック・入力・フォームイベントのハンズオンで解説する入門記事のアイキャッチ
React Event Handling入門:クリック・入力・フォームイベントをハンズオンで学ぶ

React event handlingの基本を、実際にコードを書いて動かしながら学びました。この記事では、クリックイベント、入力イベント、イベントオブジェクト、デフォルト動作の防止まで、ハンズオンで ...

続きを見る

React useState useEffect 状態管理と副作用処理の基本をハンズオンで学ぶ
React useState と useEffect を理解する — 状態管理と副作用の基本

React の useState と useEffect は、コンポーネントの状態管理と副作用処理を担う最も重要な Hook です。この記事では、実際にカウンターやタイマーを作りながら、これらの仕組み ...

続きを見る

React useContext Props drillingを解消する状態共有の仕組みをハンズオンで学ぶ記事のアイキャッチ画像
React useContextで学ぶ状態共有 — Props drillingからの脱却

Reactでコンポーネントの階層が深くなると、「親から子へ、子から孫へ」とPropsをバケツリレーのように渡し続ける場面に出会います。これが Props drilling と呼ばれる厄介な問題です。u ...

続きを見る

-dev, React