第13章
ReactとTypeScript — Props・Hooks・イベントの型付け
約5分
この章の目次開く
この章はReact入門本との接続ポイントです。React本で学んだコンポーネント・props・Hooksを、TypeScriptでどう型付けするかを整理します。
学習者Reactのpropsって、TypeScriptだとどう型を付けるのが正解なの?毎回 any にしちゃってる…。
Propsの型付け
Propsの型は type または interface で定義し、コンポーネントの引数に渡します。
type ButtonProps = {
label: string;
variant?: 'primary' | 'secondary';
disabled?: boolean;
onClick: () => void;
};
function Button({ label, variant = 'primary', disabled, onClick }: ButtonProps) {
return (
<button
className={`btn-${variant}`}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
);
}tsx
オプショナルとデフォルト値
type CardProps = {
title: string; // 必須
subtitle?: string; // 省略可能(string | undefined)
maxWidth?: number; // 省略可能
};
// デフォルト値は分割代入で設定
function Card({ title, subtitle, maxWidth = 400 }: CardProps) {
return (
<div style={{ maxWidth }}>
<h2>{title}</h2>
{subtitle && <p>{subtitle}</p>}
</div>
);
}tsx
childrenの型
| 型 | 含まれるもの | 使いどころ |
|---|---|---|
React.ReactNode | 文字列、数値、JSX、配列、null、undefined、boolean | 最も一般的。ほとんどの場合これ |
React.ReactElement | JSX要素のみ(文字列やnullは不可) | 子要素がJSXであることを保証したいとき |
string | 文字列のみ | テキストだけを受け取りたいとき |
type LayoutProps = {
children: React.ReactNode;
};
function Layout({ children }: LayoutProps) {
return (
<div className="container">
<header>ヘッダー</header>
<main>{children}</main>
</div>
);
}tsx
children の型は迷ったら React.ReactNode を使えばほぼ間違いない。
useStateの型
useState はジェネリクスで型を指定できます。
// 初期値から推論される場合(ジェネリクス不要)
const [count, setCount] = useState(0); // number
const [name, setName] = useState(''); // string
const [isOpen, setIsOpen] = useState(false); // boolean
// 初期値がnullの場合(ジェネリクスが必要)
const [user, setUser] = useState<User | null>(null);
// 配列の場合
const [items, setItems] = useState<string[]>([]);
// オブジェクトの場合
type FormData = { name: string; email: string };
const [form, setForm] = useState<FormData>({ name: '', email: '' });tsx
先生初期値の型から推論できるなら、ジェネリクスを書く必要はない。useState<number>(0) は冗長。null が初期値のときだけ明示的に書こう。
useRefの型
useRef は DOM要素を参照する場合と、値を保持する場合で書き方が変わります。
// DOM要素の参照(初期値は null)
const inputRef = useRef<HTMLInputElement>(null);
// 値の保持(初期値は具体的な値)
const countRef = useRef(0);
const timerRef = useRef<ReturnType<typeof setTimeout>>(null);tsx
// DOM参照の使用例
function SearchInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
inputRef.current?.focus(); // null チェックが必要
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>フォーカス</button>
</>
);
}tsx
イベントハンドラの型
よく使うイベント型の一覧です。
| イベント | 型 | 使うHTML要素 |
|---|---|---|
| クリック | React.MouseEvent<HTMLButtonElement> | button, div |
| 入力変更 | React.ChangeEvent<HTMLInputElement> | input, select, textarea |
| フォーム送信 | React.FormEvent<HTMLFormElement> | form |
| キー入力 | React.KeyboardEvent<HTMLInputElement> | input, div |
| フォーカス | React.FocusEvent<HTMLInputElement> | input |
// onChange
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
// onSubmit
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// フォーム送信処理
};
// onClick
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('clicked at', e.clientX, e.clientY);
};tsx
コンポーネントの型 — React.FCを使うかどうか
// ❌ React.FC を使うスタイル(現在は非推奨気味)
const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
// ✅ 関数宣言でpropsの型を直接書くスタイル(推奨)
function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>;
}tsx
React.FC は暗黙的に children を受け入れる問題(React 18で修正済み)や、ジェネリクスとの相性の悪さから、関数宣言で直接書くスタイルが主流です。
ジェネリックコンポーネント
リストやセレクトボックスなど、扱うデータの型をコンポーネント利用側が決めるパターンです。
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
};
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}tsx
// 使う側 — User型が自動推論される
type User = { id: string; name: string; age: number };
<List<User>
items={users}
renderItem={(user) => <span>{user.name}({user.age}歳)</span>}
keyExtractor={(user) => user.id}
/>tsx

実務でよく使うパターン集
コンポーネントpropsを拡張する
// HTMLのbutton属性をすべて引き継ぐ
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant: 'primary' | 'secondary';
};
function Button({ variant, className, ...rest }: ButtonProps) {
return <button className={`btn-${variant} ${className ?? ''}`} {...rest} />;
}tsx
discriminated union で状態を表現する
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function UserProfile({ state }: { state: AsyncState<User> }) {
switch (state.status) {
case 'idle': return <p>データを取得してください</p>;
case 'loading': return <p>読み込み中...</p>;
case 'success': return <p>{state.data.name}</p>; // data にアクセス可能
case 'error': return <p>エラー: {state.error}</p>;
}
}tsx
ちゃんと使うためのポイント
-
Propsの型は
typeで定義し、コンポーネントの引数に直接書く childrenはReact.ReactNodeがほぼ万能useStateの型は初期値から推論できるなら省略、null初期値のときだけ明示- イベント型はエディタの推論で調べるのが実務的
React.FCより関数宣言で直接propsの型を書くスタイルが主流- HTML属性の拡張、discriminated union は実務で頻出
次の章では、TypeScriptの実務パターンをさらに掘り下げます。
参考リンク
- React公式 — TypeScript を使用する(日本語) — React公式のTypeScriptガイド
- React TypeScript Cheatsheet(英語) — ReactのTypeScript型付けパターン集の定番リソース
TypeScriptクイズに挑戦するこの章で学んだTypeScriptの知識を、4択クイズでアウトプットして定着させよう
