📘
TypeScript入門 - Reactで使う主な型定義
はじめに
ReactアプリケーションでTypeScriptを使用する際に必要となる、基本的な型定義について解説します。TypeScriptを導入することで、型安全性が向上し、開発体験が大幅に改善されます。
コンポーネントの型定義
関数コンポーネントの基本
Reactの関数コンポーネントには、React.FC(Function Component)型を使う方法と、通常の関数として定義する方法があります。
// React.FCを使用する方法
import React from "react";
type Props = {
title: string;
count: number;
};
const Component: React.FC<Props> = ({ title, count }) => {
return (
<div>
<h1>{title}</h1>
<p>Count: {count}</p>
</div>
);
};
// 通常の関数として定義する方法(推奨)
const Component = ({ title, count }: Props): JSX.Element => {
return (
<div>
<h1>{title}</h1>
<p>Count: {count}</p>
</div>
);
};
React v18から、「暗黙的に含まれる」は削除されました。
現在では、React.FCよりも通常の関数として定義する方法が推奨されています。理由は以下の通りです:
childrenが暗黙的に含まれない(明示的に定義する必要がある)ジェネリクスの扱いがシンプル戻り値の型を明示できる
Propsの型定義
// 基本的なProps
type ButtonProps = {
label: string;
onClick: () => void;
disabled?: boolean; // オプショナル
};
// childrenを含むProps
type CardProps = {
title: string;
children: React.ReactNode;
};
// HTML要素の属性を継承
type CustomButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant: "primary" | "secondary";
};
const CustomButton = ({ variant, ...props }: CustomButtonProps) => {
return <button className={variant} {...props} />;
};
Hooksの型定義
useState
import { useState } from "react";
// 型推論が効く場合
const [count, setCount] = useState(0); // number型として推論
// 明示的に型を指定
const [user, setUser] = useState<User | null>(null);
// 複数の型を持つ場合
type Status = "idle" | "loading" | "success" | "error";
const [status, setStatus] = useState<Status>("idle");
useRef
import { useRef } from "react";
// DOM要素への参照
const inputRef = useRef<HTMLInputElement>(null);
// 使用例
const focusInput = () => {
inputRef.current?.focus();
};
return <input ref={inputRef} />;
// 値を保持する場合
const countRef = useRef<number>(0);
useEffect
import { useEffect } from "react";
// 基本的な使い方(型定義は不要)
useEffect(() => {
// 副作用の処理
const timer = setTimeout(() => {
console.log("executed");
}, 1000);
// クリーンアップ関数
return () => {
clearTimeout(timer);
};
}, []);
useContext
import { createContext, useContext } from "react";
type Theme = "light" | "dark";
type ThemeContextType = {
theme: Theme;
toggleTheme: () => void;
};
// undefinedを許容しない方法
const ThemeContext = createContext<ThemeContextType>({
theme: "light",
toggleTheme: () => {},
});
// undefinedを許容する方法(推奨)
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
};
useReducer
import { useReducer } from "react";
type State = {
count: number;
error: string | null;
};
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset" }
| { type: "error"; payload: string };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "increment":
return { ...state, count: state.count + 1 };
case "decrement":
return { ...state, count: state.count - 1 };
case "reset":
return { ...state, count: 0 };
case "error":
return { ...state, error: action.payload };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0, error: null });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
};
イベントハンドラの型定義
import { ChangeEvent, FormEvent, MouseEvent } from "react";
const Form = () => {
// input要素のchangeイベント
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
// textarea要素のchangeイベント
const handleTextAreaChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
console.log(e.target.value);
};
// select要素のchangeイベント
const handleSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
console.log(e.target.value);
};
// formのsubmitイベント
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// フォーム送信処理
};
// buttonのclickイベント
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log("clicked");
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<textarea onChange={handleTextAreaChange} />
<select onChange={handleSelectChange}>
<option value="a">A</option>
<option value="b">B</option>
</select>
<button onClick={handleClick}>Submit</button>
</form>
);
};
カスタムフックの型定義
import { useState, useEffect } from "react";
// 基本的なカスタムフック
const useCounter = (initialValue: number = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
};
// 非同期処理を含むカスタムフック
type FetchState<T> = {
data: T | null;
loading: boolean;
error: Error | null;
};
const useFetch = <T,>(url: string): FetchState<T> => {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setState({ data, loading: false, error: null });
} catch (error) {
setState({ data: null, loading: false, error: error as Error });
}
};
fetchData();
}, [url]);
return state;
};
// 使用例
type User = {
id: number;
name: string;
};
const UserProfile = () => {
const { data, loading, error } = useFetch<User>("/api/user/1");
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No data</div>;
return <div>{data.name}</div>;
};
便利なユーティリティ型
// Partial - すべてのプロパティをオプショナルに
type User = {
id: number;
name: string;
email: string;
};
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
// Required - すべてのプロパティを必須に
type RequiredUser = Required<PartialUser>;
// Pick - 特定のプロパティのみを抽出
type UserNameAndEmail = Pick<User, "name" | "email">;
// { name: string; email: string; }
// Omit - 特定のプロパティを除外
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; }
// Record - キーと値の型を指定
type UserRoles = Record<string, "admin" | "user" | "guest">;
// { [key: string]: 'admin' | 'user' | 'guest'; }
// ComponentPropsWithoutRef - コンポーネントのProps型を取得
type ButtonProps = React.ComponentPropsWithoutRef<"button">;
まとめ
ReactでTypeScriptを使用する際の主な型定義について解説しました。これらの型定義を適切に使用することで:
- 型安全性が向上し、ランタイムエラーを防げる
- IDEの補完機能が充実し、開発効率が上がる
- コードの可読性と保守性が向上する
TypeScriptの型システムは強力ですが、最初から完璧を目指す必要はありません。まずは基本的な型定義から始めて、徐々に高度な型定義を取り入れていくことをお勧めします。
Discussion
重箱の隅で恐縮ですが、
「FC を使うと children が暗黙的に含まれる」仕様は、React v18 で削除済みです。
そうでした
ご指摘ありがとうございます
修正しておきます