お疲れ。最近、君の書いたコンポーネントのコードレビューをしたんだけどさ……おっと、そんなに身構えなくていい。悪くない出来だったよ。ただ、イベントハンドラの型定義のところで、少し「もったいない書き方」をしている箇所があったんだ。
例えば、親から子へ `onClick` を渡すとき、適当に `(e: any) => void` と書いたり、毎回 `(e: React.MouseEvent
中級から一段上のシニアへステップアップするタイミングで、この「イベントハンドラの型定義」の解像度を上げておくと、コードの美しさと保守性が劇的に変わるんだ。今日はそのあたり、現場のリアルな知見を交えて徹底的に解説しようか。
—
なぜ `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
// React.MouseEventHandler
// ジェネリクスで「どの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
return (
);
};
// ==========================================
// 3. 親コンポーネント(上記を組み合わせて使う例)
// ==========================================
export const UserFormContainer: React.FC = () => {
const [keyword, setKeyword] = useState(”);
// React.ChangeEventHandler
// 引数 e の型を明示しなくても、TypeScriptが型推論を完璧にこなしてくれる。
const handleInputChange: React.ChangeEventHandler
setKeyword(e.target.value);
};
// React.MouseEventHandler
const handleSearchClick: React.MouseEventHandler
// ちなみにここで e.currentTarget を使うと、
// 型安全にHTMLButtonElementのプロパティにアクセスできる。
console.log(‘検索ボタンが押されました:’, keyword);
console.log(‘発火元要素:’, e.currentTarget.tagName);
};
return (
検索コンテナ
);
};
—
シニアが教える、ちょっとした「泥臭い」コツ
コードを読んでみてどうだい? 「あ、イベントハンドラの型をあらかじめ変数やPropsに定義しておけば、イベントリスナー側の関数で型を省略できるんだな」って気づいたなら素晴らしい。
TypeScriptの型推論は優秀だ。子コンポーネントのProps側で `React.MouseEventHandler
避けるべき「アンチパターン」
たまに見かけるのが、こんなコードだ。
// ❌ やっちゃダメな例
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の強力な「型推論」を味方につけよう。
次のプルリクエストからは、ぜひこの知見を活かして、レビューアを「おっ」と言わせるような美しい型定義を見せてくれよな。期待してるぜ!

コメント