第14章
実践パターン集 — よくある型の書き方
約4分
この章の目次開く
この章は逆引きリファレンスです。「こういうことがしたいとき、型をどう書くか」を場面ごとにまとめます。
学習者やりたいことは分かってるのに、型の書き方だけ思い出せない…ってよくあるんだよね。
APIレスポンスの型定義
APIの成功・失敗を型で表現するパターン。
// 成功レスポンス
type ApiResponse<T> = {
data: T;
meta: { total: number; page: number; perPage: number };
};
// エラーレスポンス
type ErrorResponse = {
error: { code: string; message: string };
};
// 成功 or 失敗のユニオン型
type ApiResult<T> =
| { ok: true; data: T; meta: ApiResponse<T>['meta'] }
| { ok: false; error: ErrorResponse['error'] };tsx
// 使用例 — fetch のラッパー関数
async function fetchApi<T>(url: string): Promise<ApiResult<T>> {
const res = await fetch(url);
if (!res.ok) {
const err = await res.json();
return { ok: false, error: err.error };
}
const json = await res.json();
return { ok: true, data: json.data, meta: json.meta };
}
// 呼び出し側 — 型が絞り込まれる
const result = await fetchApi<User[]>('/api/users');
if (result.ok) {
console.log(result.data); // User[]
console.log(result.meta.total);
} else {
console.error(result.error.message); // string
}tsx
if (result.ok) だけで型が絞り込まれる。
フォームの状態管理
type LoginForm = {
email: string;
password: string;
};
// フィールドごとに更新できるハンドラ
function useForm<T extends Record<string, unknown>>(initialValues: T) {
const [values, setValues] = useState(initialValues);
const handleChange = (field: keyof T, value: T[keyof T]) => {
setValues((prev) => ({ ...prev, [field]: value }));
};
const reset = () => setValues(initialValues);
return { values, handleChange, reset };
}
// 使用例
const { values, handleChange } = useForm<LoginForm>({
email: '',
password: '',
});
// handleChange('email', 'test@example.com') ✅
// handleChange('typo', 'value') ❌ コンパイルエラーtsx
環境変数の型付け
process.env の型を安全に扱うには、型定義ファイルで拡張します。
// env.d.ts(プロジェクトルートに置く)
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
JWT_SECRET: string;
NEXT_PUBLIC_API_URL: string;
NODE_ENV: 'development' | 'production' | 'test';
}
}tsx
// これで process.env.DATABASE_URL が string 型として補完される
const dbUrl = process.env.DATABASE_URL; // string
// 存在しない環境変数はエラーになる
// const x = process.env.TYPO; // ❌ プロパティが存在しないtsx
配列から型を抽出する(as const)
const statuses = ['idle', 'loading', 'success', 'error'] as const;
type Status = (typeof statuses)[number];
// 'idle' | 'loading' | 'success' | 'error'
// 配列とユニオン型を同時に使える
function isValidStatus(value: string): value is Status {
return (statuses as readonly string[]).includes(value);
}tsx
先生as const を付けないと string[] に推論されてしまい、ユニオン型が取れない。as const は「この配列はリテラル型の読み取り専用タプルだ」と宣言するもの。
オブジェクトのキーから型を作る
const routes = {
home: '/',
about: '/about',
contact: '/contact',
} as const;
type RouteName = keyof typeof routes;
// 'home' | 'about' | 'contact'
type RoutePath = (typeof routes)[RouteName];
// '/' | '/about' | '/contact'
// 型安全なナビゲーション関数
function navigate(name: RouteName) {
const path = routes[name];
window.location.href = path;
}
navigate('home'); // ✅
// navigate('typo'); // ❌ コンパイルエラーtsx
型の網羅性チェック(exhaustive check)
switch文で全ケースを処理したことをコンパイラに保証させるパターン。
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
case 'triangle':
return (shape.base * shape.height) / 2;
default: {
const _exhaustive: never = shape;
return _exhaustive;
}
}
}tsx
never に代入できなくなりコンパイルエラーで教えてくれる。
Mapped Types — 既存の型を変換する
// すべてのプロパティをオプショナルにする(Partial の内部実装)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// すべてのプロパティを読み取り専用にする(Readonly の内部実装)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// 特定のキーだけ必須にする
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
type User = { name?: string; email?: string; age?: number };
type UserWithEmail = RequireKeys<User, 'email'>;
// email は必須、name と age はオプショナルのままtsx
Template Literal Types
文字列パターンを型で表現できます。
type EventName = `on${Capitalize<'click' | 'change' | 'submit'>}`;
// 'onClick' | 'onChange' | 'onSubmit'
type CSSProperty = `${string}-${string}`;
// 'background-color', 'font-size' などtsx
// 実務的な例: APIのエンドポイント型
type ApiVersion = 'v1' | 'v2';
type Resource = 'users' | 'products' | 'orders';
type Endpoint = `/api/${ApiVersion}/${Resource}`;
// '/api/v1/users' | '/api/v1/products' | '/api/v1/orders'
// | '/api/v2/users' | '/api/v2/products' | '/api/v2/orders'tsx
Conditional Types
ライブラリの型定義を読むときに出てくるパターン。
// 基本形
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
// infer で型を抽出する
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type Result = ReturnType<Fn>; // stringtsx
// Promise の中身を取り出す
type Awaited<T> = T extends Promise<infer U> ? U : T;
type P = Awaited<Promise<string>>; // string
type Q = Awaited<string>; // string(Promiseでないならそのまま)tsx

ちゃんと使うためのポイント
- APIレスポンスは discriminated union で成功・失敗を表現する
as constで配列やオブジェクトからリテラル型を抽出できる- 環境変数は
env.d.tsで型を付けるが、実行時の存在は別途バリデーション - exhaustive check で switch 文の網羅性をコンパイラに保証させる
- Mapped Types / Conditional Types はライブラリの型定義を読むときに知っていると強い
参考リンク
- TypeScript Handbook — Utility Types(英語) — 実践パターンの土台になる公式リファレンス
- TypeScript Handbook — Conditional Types(英語) — 条件付き型の公式ガイド
- TypeScript Playground — 型の挙動をその場で確認できる公式ツール
TypeScriptクイズに挑戦するこの章で学んだTypeScriptの知識を、4択クイズでアウトプットして定着させよう
