Next.jsによるWebアプリケーション開発概論

イベント処理の基本

7Next.js基礎:ReactコンポーネントとJSX・Propsの基本
Next.jsTailwind CSSアプリ開発開発Web開発

この節で学ぶこと

この節では、React と Next.js における イベント処理の基本 を学びます。React 公式では、イベントハンドラは JSX に追加する関数であり、クリック、ホバー、入力、送信などのユーザー操作に応じて実行されると説明されています。さらに、イベントハンドラは 関数を渡す のであって、その場で 関数を呼び出す のではない、という点が強調されています。

この節の到達目標は4つです。

  1. onClickonChange などのイベントハンドラを JSX に書けることです。
  2. イベントハンドラへ 関数を渡す 書き方と、誤って その場で実行してしまう 書き方の違いを説明できることです。
  3. e.preventDefault()e.stopPropagation() の違いを理解することです。
  4. Next.js の App Router では、イベント処理を使うコンポーネントが基本的に Client Component 側にある必要があると分かることです。Next.js 公式は、状態やイベントハンドラは Client Components の代表例だと説明しています。

イベント処理とは何か

ユーザーの操作に反応する仕組み

イベント処理とは、ユーザーの操作に合わせてコードを動かす仕組みです。

React 公式では、イベントハンドラは React の JSX に追加できる関数で、クリック、ホバー、フォーカス、フォーム入力などに応じて実行されると説明されています。

たとえば、ボタンを押したときにメッセージを出す、入力欄に文字が入ったら値を読む、フォーム送信時にページの再読み込みを止める、といったことがイベント処理です。

つまりイベント処理は、画面を見せるだけの UI を、操作できる UI に変える入口です。

React 公式の学習導線でも、「Adding Interactivity」の最初にイベント処理が置かれています。

React のイベントは JSX に書く

React では HTML に onclick="..." のように文字列を書くのではなく、JSX の属性として関数を渡します。React 公式も、イベントハンドラを追加するには、まず関数を定義し、それを適切な JSX タグへ prop として渡す と説明しています。

export default function Button() {
  function handleClick() {
    alert('クリックされました')
  }

  return <button onClick={handleClick}>押す</button>
}

この形が、React におけるイベント処理の基本です。

handleClick という関数を定義し、それを onClick に渡しています。

まず最初に覚える書き方

関数を定義して渡す

React 公式では、イベントハンドラ関数はコンポーネントの中に定義し、onClick={handleClick} のように渡す形が基本だと説明しています。

また、慣例としてイベントハンドラ名は handle から始めることが多いとされています。

export default function SaveButton() {
  function handleClick() {
    alert('保存しました')
  }

  return <button onClick={handleClick}>保存</button>
}

この書き方の良いところは、処理の名前が付くことです。

あとから読む人も、「このボタンはクリック時に何をするのか」が分かりやすくなります。

その場で書くこともできる

React 公式では、短い処理ならインライン関数やアロー関数でもよいと説明しています。これらは書き方が違うだけで、意味としては同じです。

export default function DeleteButton() {
  return (
    <button onClick={() => alert('削除しました')}>
      削除
    </button>
  )
}

これは短くて便利です。

ただ、処理が長くなるなら、関数を別に切り出した方が読みやすいことが多いです。

よくある間違い

関数を渡すのではなく、呼び出してしまう

React 公式は、イベントハンドラには 関数を渡す 必要があり、関数をその場で呼び出してはいけない と強く説明しています。onClick={handleClick} は正しく、onClick={handleClick()} は誤りです。後者は、クリック時ではなくレンダリング時に実行されます。

悪い例です。

export default function BadButton() {
  function handleClick() {
    alert('クリックされました')
  }

  return <button onClick={handleClick()}>押す</button>
}

正しい例はこちらです。

export default function GoodButton() {
  function handleClick() {
    alert('クリックされました')
  }

  return <button onClick={handleClick}>押す</button>
}

この違いは、見た目だとかなり小さいです。

でも意味は大きく違います。

React 公式も、この違いは subtle だが重要だと説明しています。

アロー関数でも同じ落とし穴がある

React 公式は、インラインでも同じで、onClick={() => alert('...')} は正しいが、onClick={alert('...')} はレンダリング時に実行されると説明しています。

悪い例です。

export default function BadAlertButton() {
  return <button onClick={alert('クリック')}>押す</button>
}

正しい例です。

export default function GoodAlertButton() {
  return <button onClick={() => alert('クリック')}>押す</button>
}

ここは初心者がかなりつまずきやすいところです。

まずは「イベントハンドラには、実行結果ではなく、関数そのものを渡す」と覚えるのが安全です。

props とイベントハンドラ

イベントハンドラは props を読める

React 公式では、イベントハンドラはコンポーネントの中で定義されるため、そのコンポーネントの props にアクセスできると説明しています。

type AlertButtonProps = {
  message: string
  children: React.ReactNode
}

function AlertButton({ message, children }: AlertButtonProps) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  )
}

この例では、クリック時に message props を読んでいます。

つまり、props とイベント処理は自然につながります。

前節で学んだ props が、ここで動き始めるわけです。

親から子へイベントハンドラを渡せる

React 公式では、親コンポーネントが子コンポーネントへイベントハンドラを props として渡す方法も説明しています。たとえば ButtononClick を受け取り、そのまま内部の <button> へ渡す形です。

type ButtonProps = {
  onClick: () => void
  children: React.ReactNode
}

function Button({ onClick, children }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>
}

function PlayButton() {
  function handlePlayClick() {
    alert('再生します')
  }

  return <Button onClick={handlePlayClick}>再生</Button>
}

この設計の良いところは、見た目の部品 と 振る舞い を分けやすいことです。

React 公式も、デザインシステムではボタンの見た目だけを部品化し、振る舞いは親が渡す形がよくあると説明しています。

イベントハンドラの名前

組み込み要素では onClick などを使う

React 公式では、組み込み要素である <button><div> などは、ブラウザのイベント名に対応した onClick などの prop を使うと説明しています。

たとえば、

  • onClick
  • onChange
  • onSubmit
  • onMouseEnter などです。

自作コンポーネントでは名前を自由に決められる

React 公式では、自作コンポーネントのイベント handler props は、自分で好きな名前にできると説明しています。慣例として on + 大文字始まりがよく使われます。

type ToolbarProps = {
  onPlayMovie: () => void
  onUploadImage: () => void
}

function Toolbar({ onPlayMovie, onUploadImage }: ToolbarProps) {
  return (
    <div>
      <button onClick={onPlayMovie}>再生</button>
      <button onClick={onUploadImage}>アップロード</button>
    </div>
  )
}

これはかなり実践的です。

onClick という技術名より、onPlayMovie のように アプリの意味に沿った名前 の方が、親側から見ると分かりやすいことがあります。React 公式もこの命名を例で紹介しています。

イベントオブジェクト

イベントハンドラは event object を受け取れる

React 公式では、イベントハンドラは最初の引数として event object を受け取り、慣例的に e と書くことが多いと説明しています。このオブジェクトを使って、イベントの情報を読んだり、伝播を止めたりできます。

export default function InputExample() {
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    console.log(e.target.value)
  }

  return <input onChange={handleChange} />
}

ここでは e.target.value を読んで入力値を取得しています。

こうしたイベントオブジェクトの扱いは、フォーム処理で特によく使います。

preventDefaultstopPropagation

React 公式は、この2つはどちらも useful だが unrelated、つまり役割が違うと明確に説明しています。

  • e.stopPropagation() は、上位要素へイベントが伝わるのを止める
  • e.preventDefault() は、ブラウザの既定動作を止める という違いです。

イベントの伝播

イベントは上へ伝わる

React 公式では、イベントは upward に伝播すると説明しています。ボタンのクリックが親の <div>onClick にも届くことがあります。

export default function Toolbar() {
  return (
    <div onClick={() => alert('ツールバーがクリックされました')}>
      <button onClick={() => alert('ボタンがクリックされました')}>
        押す
      </button>
    </div>
  )
}

この場合、ボタンを押すとボタン側だけでなく親の div 側も反応することがあります。

初心者には少し意外ですが、React 公式もこれを具体例で説明しています。

stopPropagation で伝播を止める

React 公式では、親へ到達させたくない場合は e.stopPropagation() を呼ぶよう説明しています。

type ButtonProps = {
  onClick: () => void
  children: React.ReactNode
}

function Button({ onClick, children }: ButtonProps) {
  return (
    <button
      onClick={(e) => {
        e.stopPropagation()
        onClick()
      }}
    >
      {children}
    </button>
  )
}

export default function Toolbar() {
  return (
    <div onClick={() => alert('ツールバー')}>
      <Button onClick={() => alert('再生')}>再生</Button>
    </div>
  )
}

この例では、ボタンのクリックが親へ上がらないので、ボタン側だけが動きます。

React 公式は、イベントには capture phase もあるが、アプリコードではまず通常の伝播と stopPropagation() を押さえれば十分だと読み取れる説明をしています。

既定動作を止める

フォーム送信では preventDefault がよく使われる

React 公式では、<form> の submit イベントにはブラウザの既定動作としてページ再読み込みがあり、e.preventDefault() を呼ぶことで止められると説明しています。

export default function Signup() {
  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    alert('送信しました')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input />
      <button>送信</button>
    </form>
  )
}

このコードでは、送信してもページはリロードされません。

フォーム処理を React で制御したいときの基本です。

preventDefaultstopPropagation は別物

React 公式は、ここをかなりはっきり区別しています。

  • preventDefault() はブラウザの既定動作を止める
  • stopPropagation() は親要素のハンドラ発火を止める

です。 この2つを混同すると、意図した動きにならないことがあります。

たとえばフォーム送信のページ再読み込みを止めたいなら preventDefault() であって、stopPropagation() ではありません。

イベント処理と state の関係

イベントハンドラは state 更新の入口になる

React 公式の「Adding Interactivity」では、画面上の変化はユーザー入力に応じて起こり、React では時間とともに変わるデータを state と呼ぶと説明しています。また、useState Hook によって state を持ち、更新関数で変えられると説明しています。

たとえば、クリックで数を増やすボタンはこう書けます。

'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)
  }

  return (
    <button onClick={handleClick}>
      {count}
    </button>
  )
}

ここではイベント処理と state 更新がつながっています。

イベント処理だけでも学べますが、実際の React アプリでは イベント → state 更新 → 画面変化 の流れになることがとても多いです。

Next.js での注意点

イベント処理は Client Component 側で使う

Next.js の App Router では、状態やイベントハンドラ、useStateuseEffect、ブラウザ API などは Client Components 側の機能として説明されています。Client Component を作るには、ファイル先頭に 'use client' を書きます。

'use client'

export default function ClickButton() {
  return <button onClick={() => alert('クリック')}>押す</button>
}

Next.js 公式の例でも、'use client' を付けた Counter コンポーネントの中で useStateonClick を使っています。

なぜ 'use client' が必要なのか

Next.js 公式では、"use client" は Server と Client の境界を宣言するものであり、ファイルを Client Component にすると、その import と子コンポーネントも client bundle の一部になると説明しています。初回ロード時には、HTML がまず表示され、その後 JavaScript によって Client Components が hydrate され、イベントハンドラが DOM に取り付けられて interactive になると説明されています。

つまり、イベント処理は「書けば勝手に効く」のではなく、クライアント側で hydration されて初めて反応できる わけです。ここは Next.js を使ううえでかなり大切な前提です。

実践的な基本パターン

ボタンのクリック

'use client'

export default function AlertButton() {
  function handleClick() {
    alert('保存しました')
  }

  return <button onClick={handleClick}>保存</button>
}

この形は、最も基本です。

まずはここを確実に書けることが大事です。

入力値を読む

'use client'

export default function InputLogger() {
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    console.log(e.target.value)
  }

  return <input onChange={handleChange} />
}

フォーム系では、この形が頻出です。

入力イベントと event object を結び付けて理解する練習になります。

フォーム送信を制御する

'use client'

export default function ContactForm() {
  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    alert('送信処理をここへ書けます')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" />
      <button>送信</button>
    </form>
  )
}

この形は、問い合わせフォームや検索フォームの基本です。

ブラウザ任せではなく React 側で制御したいときに重要になります。

ミニ練習問題

問1

React のイベントハンドラの基本として正しいものはどれですか。

A. イベントハンドラには関数の実行結果を渡す

B. イベントハンドラには関数そのものを渡す

C. イベントハンドラは CSS ファイルに書く

D. イベントハンドラは public フォルダに置く

答え

B

解説

React 公式では、イベントハンドラは passed, not called だと説明しています。つまり onClick={handleClick} は正しく、onClick={handleClick()} は誤りです。

問2

e.preventDefault() の主な役割として正しいものはどれですか。

A. 親要素への伝播を止める

B. ブラウザの既定動作を止める

C. state を初期化する

D. props を削除する

答え

B

解説

React 公式では、e.preventDefault() はフォーム送信時のページリロードなど、既定のブラウザ動作を防ぐために使うと説明しています。

問3

e.stopPropagation() の主な役割として正しいものはどれですか。

A. 入力値を空にする

B. 親要素へイベントが伝わるのを止める

C. デフォルトの送信動作を止める

D. JSX を再描画しないようにする

答え

B

解説

React 公式では、e.stopPropagation() はイベントが上位要素へ bubbling していくのを止めるために使うと説明しています。

問4

Next.js App Router で onClickuseState を使うコンポーネントに必要なものはどれですか。

A. export const dynamic = 'force-static' B. use server C. ファイル先頭の 'use client' D. robots.txt

答え

C

解説

Next.js 公式では、状態やイベントハンドラは Client Components の代表例であり、Client Component を作るにはファイル先頭へ 'use client' を追加すると説明しています。

問5

React で親が子へイベント処理を渡す設計として正しいものはどれですか。

A. 子が親の state を直接変更する

B. 親がイベントハンドラを props として渡す

C. すべてのイベントを window に直接登録する

D. onClick は組み込み要素でしか使えないので自作コンポーネントでは渡せない

答え

B

解説

React 公式では、親が子へイベントハンドラを prop として渡し、子が内部の <button> などへそのまま渡す設計を紹介しています。

まとめ

イベント処理は、React の UI を「見えるだけ」から「操作できるもの」へ変える基本です。

React 公式では、イベントハンドラは JSX に追加する関数であり、クリックや入力、送信などに応じて実行されると説明しています。

さらに、イベントハンドラは 関数を渡す のであって その場で呼ぶ のではないこと、preventDefault()stopPropagation() は役割が違うことも重要な基本です。

Next.js では、これらのイベント処理は主に Client Components 側で使います。

'use client' を境界として、そこで state や event handlers を使い、ブラウザ側で hydration されて初めて UI が interactive になります。

ここが分かると、React のイベント処理と Next.js の Client Component の役割がきれいにつながります。

参考文献

  • React Docs, Responding to Events
  • React Docs, Adding Interactivity
  • React Docs, State: A Component’s Memory
  • React DOM Docs, Common components (e.g. <div>)
  • Next.js Docs, Getting Started: Server and Client Components
教材トップへ戻る