【実務・中級編】 イベントハンドラPropsの型定義 – React実践ガイド

お疲れ。最近、君の書いたコンポーネントのコードレビューをしたんだけどさ……おっと、そんなに身構えなくていい。悪くない出来だったよ。ただ、イベントハンドラの型定義のところで、少し「もったいない書き方」をしている箇所があったんだ。

例えば、親から子へ `onClick` を渡すとき、適当に `(e: any) => void` と書いたり、毎回 `(e: React.MouseEvent) => void` みたいに冗長な型をガリガリ書いて消耗してないかい?

中級から一段上のシニアへステップアップするタイミングで、この「イベントハンドラの型定義」の解像度を上げておくと、コードの美しさと保守性が劇的に変わるんだ。今日はそのあたり、現場のリアルな知見を交えて徹底的に解説しようか。

—

なぜ `React.EventHandler` 系型を知る必要があるのか?

実務でコードを書いていると、ボタンのクリックイベントだけでなく、テキストエリアの変更(`onChange`)、フォームの送信(`onSubmit`)、果てはマウスのホバーやドラッグ&ドロップまで、無数のイベントに遭遇する。

ここで初心者がやりがちなのが、イベントオブジェクト(`e`)の型を毎回手動でインポートして組み立てることだ。
「ええと、div要素のonClickだから… `React.MouseEvent` だっけ?」……いやいや、そんなことやってたら手が何本あっても足りないし、何よりコードがノイズまみれになる。

ブラウザの裏側では、DOMのネイティブなイベント(`MouseEvent` や `KeyboardEvent` など)が発火し、Reactはその生々しいイベントをラップして、クロスブラウザで一貫して扱える「SyntheticEvent(合成イベント)」として僕らに手渡してくれている。
Reactの型定義(`@types/react`)は、このSyntheticEventをベースによく練られたユーティリティ型を用意してくれているんだ。これを使わない手はない。

現場で即戦力になる「使い分け」のベストプラクティス

結論から言おう。実務で頻繁に使うのは、主に以下の3つのパターンだ。

1. 特定の要素に紐づくイベントを直接受け取る場合:`React.ComponentProps<"button">[“onClick”]` などのユーティリティ型、または直接ジェネリクスを指定する。
2. コンポーネントの汎用的なPropsとして再利用する場合:`React.MouseEventHandler` や `React.ChangeEventHandler` などの専用ハンドラ型。
3. DOM要素の型とイベントの型を綺麗に一致させたい場合:ジェネリクスにHTML要素(例: `HTMLButtonElement`)を渡す。

百聞は一見にしかず。実際のコードを見ながら解説しよう。

実用サンプルコード

以下のコードは、現場でよくある「再利用性の高いカスタムボタンと、それを組み込むフォーム画面」を想定した例だ。そのままIDEに放り込んで動かせるように書いておいた。

import React, { useState } from ‘react’;

// ==========================================
// 1. 子コンポーネント(再利用可能なボタン)
// ==========================================

// 技ありポイント:
// 単に `onClick: (e: React.MouseEvent) => void` と書くよりも、
// React.MouseEventHandler を使う方が、Reactの標準的な作法に準拠しており、
// ジェネリクスで「どのHTML要素から発火したイベントか」を明示できるため保守性が高い。
interface ActionButtonProps {
// ボタン内のテキスト
label: string;
// クリックイベントのハンドラ(HTMLButtonElementを対象とするマウスイベント)
onClick: React.MouseEventHandler;
// 無効化フラグ(オプショナル)
disabled?: boolean;
}

export const ActionButton: React.FC = ({
label,
onClick,
disabled = false,
}) => {
return (

);
};

// ==========================================
// 2. 別のパターン:input等のonChangeの型定義
// ==========================================

interface SearchInputProps {
value: string;
// 入力値変更のイベントハンドラ。HTMLInputElementを対象とする。
onChange: React.ChangeEventHandler;
}

export const SearchInput: React.FC = ({ value, onChange }) => {
return (

);
};

// ==========================================
// 3. 親コンポーネント(上記を組み合わせて使う例)
// ==========================================

export const UserFormContainer: React.FC = () => {
const [keyword, setKeyword] = useState(”);

// React.ChangeEventHandler と型が完全に一致するため、
// 引数 e の型を明示しなくても、TypeScriptが型推論を完璧にこなしてくれる。
const handleInputChange: React.ChangeEventHandler = (e) => {
setKeyword(e.target.value);
};

// React.MouseEventHandler に完全準拠
const handleSearchClick: React.MouseEventHandler = (e) => {
// ちなみにここで e.currentTarget を使うと、
// 型安全にHTMLButtonElementのプロパティにアクセスできる。
console.log(‘検索ボタンが押されました:’, keyword);
console.log(‘発火元要素:’, e.currentTarget.tagName);
};

return (

検索コンテナ


);
};

—

シニアが教える、ちょっとした「泥臭い」コツ

コードを読んでみてどうだい? 「あ、イベントハンドラの型をあらかじめ変数やPropsに定義しておけば、イベントリスナー側の関数で型を省略できるんだな」って気づいたなら素晴らしい。

TypeScriptの型推論は優秀だ。子コンポーネントのProps側で `React.MouseEventHandler` と厳密に縛っておけば、親側で関数を定義するときに `(e) => { … }` と書いても、`e` は勝手に適切なイベント型に推論される。わざわざ自分で長い型をタイピングする必要なんてないんだよ。

避けるべき「アンチパターン」

たまに見かけるのが、こんなコードだ。

// ❌ やっちゃダメな例
type BadProps = {
// 何が飛んでくるか分からない(anyは言語道断)
onClick: (e: any) => void;

// イベントオブジェクトを取らないように見えて、実は引数を受け取れちゃうガバガバな型
onCustomClick: () => void;
}

`() => void` という型をイベントハンドラに当ててしまうと、親側でイベントオブジェクト(`e`)を受け取って `e.preventDefault()` を呼び出したいときに、TypeScriptのコンパイラに怒られたり、無理やりキャストするハメになったりしてコードが汚染される。
たとえ今の時点でイベント引数を使わなそうだとしても、将来の拡張性やReactの標準仕様へのリスペクトを込めて、適切な `EventHandler` 型や関数シグネチャ(`(e: React.MouseEvent) => void` など)を採用するのがプロの仕事というものだ。

まとめ

フロントエンドの設計において、「型」は単なるエラーチェックの道具じゃない。「このコンポーネントと親の間には、こういう契約(Contract)があるんだ」というチームへの美しいラブレターなんだよ。

今日のポイントを振り返ろう:

  • イベントハンドラには `React.EventHandler` 系(`MouseEventHandler`, `ChangeEventHandler` など)を積極的に使おう。
  • ジェネリクスに具体的なHTML要素(`HTMLButtonElement` など)を指定して、型安全性を極限まで高めよう。
  • 正しい型定義のおかげで生まれる、TypeScriptの強力な「型推論」を味方につけよう。

次のプルリクエストからは、ぜひこの知見を活かして、レビューアを「おっ」と言わせるような美しい型定義を見せてくれよな。期待してるぜ!

コメント

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