「TypeScriptをReactで使いたいけど、型の書き方がわからない」「useStateやuseEffectをTypeScriptで正しく書く方法は?」という疑問を解決します。この記事ではReact × TypeScriptの実践的な書き方をコード例たっぷりで解説します。
環境構築:Vite + React + TypeScript
2026年現在、ReactのTypeScript開発にはViteが最もおすすめです。CRAよりも起動が速く、設定がシンプルです。
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
基本的な型定義
Props の型定義
// interface を使った Props の定義
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean; // ? でオプショナル
variant?: 'primary' | 'secondary' | 'danger'; // Union型
}
const Button: React.FC<ButtonProps> = ({
label,
onClick,
disabled = false,
variant = 'primary'
}) => {
return (
<button
className={variant}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
);
};
useState の型定義
import { useState } from 'react';
// プリミティブ型
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
// オブジェクト型
interface User {
id: number;
name: string;
email: string;
}
const [user, setUser] = useState<User | null>(null);
// 配列型
const [items, setItems] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);
useEffect の型定義
import { useState, useEffect } from 'react';
interface Post {
id: number;
title: string;
body: string;
}
function PostList() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error('Network response was not ok');
const data: Post[] = await response.json();
setPosts(data);
} catch (err) {
setError(err instanceof Error ? err.message : '不明なエラー');
} finally {
setLoading(false);
}
};
fetchPosts();
}, []);
if (loading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error}</div>;
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
useRef の型定義
import { useRef } from 'react';
function TextInput() {
// DOM要素への参照
const inputRef = useRef<HTMLInputElement>(null);
// ミュータブルな値(再レンダリングなし)
const timerRef = useRef<number | null>(null);
const handleFocus = () => {
inputRef.current?.focus(); // Optional chaining で安全にアクセス
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>フォーカス</button>
</div>
);
}
カスタムフックの型定義
// useFetch カスタムフック
interface FetchState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
fetch(url)
.then(res => res.json())
.then((data: T) => setState({ data, loading: false, error: null }))
.catch(err => setState({ data: null, loading: false, error: err.message }));
}, [url]);
return state;
}
// 使用例
function App() {
const { data, loading, error } = useFetch<User[]>('/api/users');
// ...
}
イベントハンドラーの型定義
// よく使うイベントハンドラーの型
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// フォーム送信処理
};
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.textContent);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
// Enterキー処理
}
};
よくある TypeScript エラーと解決策
Object is possibly ‘null’
// NG
const value = ref.current.value; // Error: Object is possibly 'null'
// OK(Optional chaining)
const value = ref.current?.value;
// OK(Non-null assertion)
const value = ref.current!.value; // nullでないと確信できる場合のみ
Type ‘string | undefined’ is not assignable to type ‘string’
// NG
const name: string = user?.name; // Error
// OK(デフォルト値)
const name: string = user?.name ?? 'Unknown';
// OK(型ガード)
if (user?.name) {
const name: string = user.name; // narrowing されて string 確定
}
まとめ
React × TypeScriptの型定義は最初は戸惑いますが、慣れるとコードの品質と開発体験が劇的に向上します。特にuseState・useRef・カスタムフックの型定義をマスターすることで、大規模アプリケーション開発でのバグを大幅に減らせます。今回紹介したパターンを繰り返し使って身に付けていきましょう。