React におけるTypeScriptの活用
1. ReactでTypeScriptを使う理由
TypeScriptは、以下の機能によってReact開発を強化します:
- Props、State、コンテキストに対する型安全性(Type safety)
- IDEによる高度な自動補完とリファクタリング支援
- 開発中における早期のエラー検知
注記: このチュートリアルは、Reactに関する基礎知識があることを前提としています。
もしReactが初めての場合は、まず「Reactチュートリアル」を確認することをお勧めします。
2. 開発の始め方
Viteを使用して、新しいReact + TypeScriptアプリを作成します:
例:TypeScriptサーバーを起動する
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run devtsconfig.json には、以下の推奨されるコンパイラオプションを含める必要があります:
例:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}注記: 最高の型安全性を維持するために、strict モードは有効にしたままにしてください。
上記のオプションは、ViteおよびCreate React Appで適切に動作します。
3. コンポーネントの型定義
TypeScriptでPropsを定義し、関数コンポーネントで使用します:
例:
// Greeting.tsx
type GreetingProps = {
name: string;
age?: number; // オプショナルなプロパティ
};
export function Greeting({ name, age }: GreetingProps) {
return (
<div>
<h2>こんにちは、{name}さん!</h2>
{age !== undefined && <p>あなたは {age} 歳です</p>}
</div>
);
}4. 一般的なパターン
4.1 型安全なイベント
インプットやボタンのイベントハンドラに型を付けます:
例:
// インプットの変更イベント
function NameInput() {
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
return <input onChange={handleChange} />;
}
// ボタンのクリックイベント
function SaveButton() {
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
}
return <button onClick={handleClick}>保存する</button>;
}4.2 useStateの型定義
数値、Union型、およびnullを許容する値に対して、明示的な型を使用します:
例:
const [count, setCount] = React.useState<number>(0);
const [status, setStatus] = React.useState<'idle' | 'loading' | 'error'>('idle');
type User = { id: string; name: string };
const [user, setUser] = React.useState<User | null>(null);4.3 useRefとDOM要素
DOMノードに型を付与して、プロパティに安全にアクセスできるようにします:
例:
function FocusInput() {
const inputRef = React.useRef<HTMLInputElement>(null);
return <input ref={inputRef} onFocus={() => inputRef.current?.select()} />;
}4.4 Childrenの型定義
React.ReactNode 型を使用して children を受け取ります:
例:
type CardProps = { title: string; children?: React.ReactNode };
function Card({ title, children }: CardProps) {
return (
<div>
<h2>{title}</h2>
{children}
</div>
);
}4.5 ジェネリクスを使用したFetchヘルパー
ジェネリクスを使用してAPIレスポンスに型を付けます:
例:
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error('ネットワークエラーが発生しました');
return res.json() as Promise<T>;
}
// 非同期関数やコンポーネントのエフェクト内での使用例
async function loadPosts() {
type Post = { id: number; title: string };
const posts = await fetchJson<Post[]>("/api/posts");
console.log(posts);
}4.6 最小限のコンテキストとカスタムフック
型定義されたコンテキストとヘルパーフックの実装例です:
例:
type Theme = 'light' | 'dark';
const ThemeContext = React.createContext<{ theme: Theme; toggle(): void } | null>(null);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = React.useState<Theme>('light');
const value = { theme, toggle: () => setTheme(t => (t === 'light' ? 'dark' : 'light')) };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
function useTheme() {
const ctx = React.useContext(ThemeContext);
if (!ctx) throw new Error('useThemeはThemeProviderの中で使用する必要があります');
return ctx;
}5. ViteのTypeScript型定義
型定義の欠落を防ぐために、Viteのアンビエント型を追加します。
例:
// src/vite-env.d.ts
/// <reference types="vite/client" />または、tsconfig.json に追加します:
例:
{
"compilerOptions": {
"types": ["vite/client"]
}
}6. React.FCについて
明示的に型定義された関数コンポーネント(直接的な型指定)を推奨します。React.FC は任意です。これは暗黙的に children を追加しますが、現在のReact開発では必須ではありません。
7.baseUrlとpaths(オプション)
これらを設定することで、バンドラーがサポートしている場合にインポートを簡素化できます。
例:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}Viteや tsconfig-paths などのツールでパスエイリアスが設定されている場合にのみ構成してください。