メインコンテンツまでスキップ

イベントハンドリング

この章では、Reactでのイベント処理について学びます。

この章で学ぶこと

  • イベントハンドラーの基本的な書き方
  • TypeScriptでのイベント型の指定
  • イベントの伝播と制御
  • イベントハンドラーの命名規則
備考

この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。

イベントハンドラーの基本

Reactでは、イベントハンドラーをキャメルケースで指定します。※06章で学んだように、関数をPropsとして渡すこともできます。

function Button() {
const handleClick = () => {
console.log('クリックされました')
}

return <button onClick={handleClick}>クリック</button>
}
警告

onClick={handleClick}は関数の参照を渡しています。onClick={handleClick()}と書くと、レンダリング時に即座に実行されてしまうので注意してください。

// OK: クリック時に実行される
<button onClick={handleClick}>

// NG: レンダリング時に即座に実行される
<button onClick={handleClick()}>

インラインでの記述

簡単な処理はインラインで記述できます。

function Button() {
return (
<button onClick={() => console.log('クリック')}>
クリック
</button>
)
}

イベントオブジェクト

イベントハンドラーは、イベントオブジェクトを引数として受け取ります。

function Input() {
// React.ChangeEvent<HTMLInputElement> はinput要素の変更イベントの型
// <HTMLInputElement> はイベントが発生した要素の型を指定
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// event.target で入力欄の要素にアクセス
// event.target.value で入力された値を取得
console.log(event.target.value)
}

return <input onChange={handleChange} />
}
SyntheticEventとは?

Reactのイベントオブジェクトは「SyntheticEvent(合成イベント)」と呼ばれます。これはブラウザ間の差異を吸収し、一貫したAPIを提供するためのラッパーです。

// React.ChangeEvent は SyntheticEvent の一種
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// e.nativeEvent で元のブラウザイベントにアクセス可能
console.log(e.nativeEvent)
}

ほとんどの場合、SyntheticEventのAPIだけで十分です。

主要なイベント型

イベント発生タイミング
onClickReact.MouseEventクリック時
onChangeReact.ChangeEvent値変更時
onSubmitReact.FormEventフォーム送信時
onKeyDownReact.KeyboardEventキー押下時
onFocusReact.FocusEventフォーカス時
onBlurReact.FocusEventフォーカス解除時

クリックイベント

function ClickExample() {
// React.MouseEvent<HTMLButtonElement> はボタンのマウスイベントの型
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// clientX, clientY でクリック位置(ブラウザ表示領域の座標)を取得
console.log('クリック位置:', event.clientX, event.clientY)
}

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

試してみよう: クリックカウンター

ボタンをクリックするたびにカウントが増え、コンソールに「○回目のクリック」と表示されるコンポーネントを作ってみましょう。

ヒント
  1. useState(0)でカウントを管理
  2. handleClick内でsetCountconsole.logを呼び出す
  3. countはそのレンダー時点の値で固定されているため、新しい値を変数に格納してから使う
回答と解説
import { useState } from 'react'
import './App.css'

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

const handleClick = () => {
// count はこのレンダー時点の値で固定されているため、更新後の値を
// 使いたい場合は先に計算して変数に格納してから使う
const newCount = count + 1
setCount(newCount)
// count をそのままログに出すと、更新前の値が表示されてしまう
console.log(`${newCount}回目のクリック`)
}

return (
<div>
<p>クリック回数: {count}</p>
<button onClick={handleClick}>クリック</button>
</div>
)
}

export default function App() {
return <ClickCounter />
}

countはそのレンダー時点の値に固定されている(スナップショット)ため、コンソールログに新しい値を表示するには、count + 1を一度変数に格納してから使用しています。これにより、表示とログが常に一致します。

入力イベント

function InputExample() {
const [value, setValue] = useState('')

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// 入力欄の現在の値をStateに保存
setValue(event.target.value)
}

return (
<div>
{/* value={value} で入力欄の値をStateと同期(制御コンポーネント) */}
<input value={value} onChange={handleChange} />
<p>入力値: {value}</p>
</div>
)
}

フォーム送信イベント

function FormExample() {
const [name, setName] = useState('')

const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value)
}

// フォーム送信時のハンドラ
// React.FormEvent<HTMLFormElement> はフォーム送信イベントの型
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
// ブラウザのデフォルト動作(ページ遷移)を防止
// これがないとページがリロードされてしまう
event.preventDefault()
console.log('送信:', name)
}

return (
// type="submit" のボタンをクリック、または Enter キーで onSubmit が発火
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={handleNameChange}
/>
<button type="submit">送信</button>
</form>
)
}

キーボードイベント

function KeyboardExample() {
// React.KeyboardEvent<HTMLInputElement> はキーボードイベントの型
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
// event.key で押されたキーを文字列で取得
// 'Enter', 'Escape', 'ArrowUp' など
if (event.key === 'Enter') {
console.log('Enterキーが押されました')
}
}

return <input onKeyDown={handleKeyDown} placeholder="Enterで送信" />
}

試してみよう: キーボード入力表示

入力フィールドで押されたキーをリアルタイムで表示するコンポーネントを作ってみましょう。

ヒント
  1. onKeyDownイベントでevent.keyを取得
  2. useState('')で最後に押されたキーを管理
  3. ||演算子で未入力時のデフォルト表示を設定
回答と解説
import { useState } from 'react'
import './App.css'

function KeyDisplay() {
const [lastKey, setLastKey] = useState('')

const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
setLastKey(event.key)
}

return (
<div>
<input
onKeyDown={handleKeyDown}
placeholder="キーを押してください"
/>
<p>押されたキー: {lastKey || '(なし)'}</p>
</div>
)
}

export default function App() {
return <KeyDisplay />
}

event.keyでどのキーが押されたかを取得できます。EnterEscapeArrowUpなど、特殊キーも文字列として取得されます。||演算子で未入力時のデフォルト表示を設定しています。

イベントハンドラーに引数を渡す

追加の引数を渡したい場合は、アロー関数でラップします。

function ItemList() {
const items = ['Apple', 'Banana', 'Orange']

// 複数の引数を受け取るハンドラー
const handleClick = (item: string, index: number) => {
console.log(`${index}: ${item}がクリックされました`)
}

return (
<ul>
{items.map((item, index) => (
<li key={item}>
{/* onClick={() => handleClick(item, index)} のように
アロー関数でラップすることで、引数を渡せる
onClick={handleClick(item, index)} と書くと
レンダリング時に即座に実行されてしまうので注意 */}
<button onClick={() => handleClick(item, index)}>
{item}
</button>
</li>
))}
</ul>
)
}

イベントの伝播

イベントバブリング

イベントは子要素から親要素に伝播(バブリング)します。

function BubblingExample() {
return (
<div onClick={() => console.log('親がクリック')}>
<button onClick={() => console.log('子がクリック')}>
クリック
</button>
</div>
)
// ボタンクリック時:
// "子がクリック"
// "親がクリック"
}

伝播の停止

stopPropagation()でイベントの伝播を止められます。

function StopPropagationExample() {
return (
<div onClick={() => console.log('親がクリック')}>
<button onClick={(e) => {
e.stopPropagation()
console.log('子がクリック')
}}>
クリック
</button>
</div>
)
// ボタンクリック時: "子がクリック" のみ
}

試してみよう: イベント伝播の確認

親要素と子要素にそれぞれonClickを設定し、stopPropagation()の動作を確認してみましょう。

ヒント
  1. 親要素(<div>)と子要素(<button>)それぞれにonClickを設定
  2. 子要素のハンドラーでe.stopPropagation()を呼び出す
  3. コメントアウトして動作の違いを確認
回答と解説
import './App.css'

function PropagationExample() {
const handleParentClick = () => {
console.log('親要素がクリックされました')
}

const handleChildClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log('子要素がクリックされました')
// event.stopPropagation() // コメントを外すと伝播が止まる
}

return (
<div
onClick={handleParentClick}
style={{ padding: '20px', backgroundColor: '#eee' }}
>
<p>親要素(ここをクリック)</p>
<button onClick={handleChildClick}>
子要素(ボタン)
</button>
</div>
)
}

export default function App() {
return <PropagationExample />
}

デフォルトではボタンをクリックすると「子要素がクリックされました」→「親要素がクリックされました」の順で表示されます。event.stopPropagation()のコメントを外すと、子要素のログのみが表示され、親要素への伝播が止まることが確認できます。

デフォルト動作の防止

preventDefault()でブラウザのデフォルト動作を防止できます。

function PreventDefaultExample() {
// React.MouseEvent<HTMLAnchorElement> はリンク要素のマウスイベントの型
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
// preventDefault() でブラウザのデフォルト動作(ページ遷移)を防止
// フォームの onSubmit でも同様にページリロードを防げる
event.preventDefault()
console.log('リンククリック(遷移なし)')
}

return (
<a href="https://example.com" onClick={handleClick}>
リンク
</a>
)
}

イベントハンドラーの命名規則

慣例として、イベントハンドラーはhandleで始める名前を付けます。

function Form() {
const handleSubmit = () => { /* ... */ }
const handleNameChange = () => { /* ... */ }
const handleEmailChange = () => { /* ... */ }

// Propsとして渡す場合は on で始める
return <Button onClick={handleSubmit} />
}

onとhandleの使い分け

場所プレフィックス意味
Props(受け取る側)ononClick「クリック時に
関数(定義する側)handlehandleClick「クリックを処理する
// 子コンポーネント: Propsは on で受け取る
function Button({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>クリック</button>
}

// 親コンポーネント: 関数は handle で定義する
function App() {
const handleClick = () => {
console.log('クリックされました')
}

return <Button onClick={handleClick} />
}

まとめ

  • イベントはキャメルケースで指定(onClickonChange
  • TypeScriptでイベントの型を指定して安全に扱う
  • preventDefault()でデフォルト動作を防止
  • stopPropagation()でイベント伝播を停止
  • ハンドラー名はhandleで始める

次の章では、条件分岐とリスト表示について学びます。

次に読む