TypeScript ユーティリティ型とは?
TypeScriptには、既存の型を変換して新しい型を作る**ユーティリティ型(Utility Types)**が組み込まれています。これらを使いこなすことで、型定義の重複を減らし、保守性の高いコードが書けます。
基準となる型
この記事では、以下の型を基準に各ユーティリティ型を説明します:
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
Partial<T> — 全プロパティをオプショナルに
type PartialUser = Partial<User>;
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// isActive?: boolean;
// }
実用例: 更新APIの引数
async function updateUser(id: number, data: Partial<User>): Promise<User> {
// 変更したいフィールドだけ渡せる
return await db.users.update(id, data);
}
// 名前だけ更新
await updateUser(1, { name: "新しい名前" });
// メールとアクティブ状態を更新
await updateUser(1, { email: "[email protected]", isActive: false });
Required<T> — 全プロパティを必須に
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
type RequiredConfig = Required<Config>;
// {
// host: string;
// port: number;
// debug: boolean;
// }
実用例: デフォルト値とマージ
function createConfig(options: Config): RequiredConfig {
return {
host: options.host ?? "localhost",
port: options.port ?? 3000,
debug: options.debug ?? false,
};
}
Pick<T, K> — 特定のプロパティだけ抽出
type UserSummary = Pick<User, "id" | "name">;
// {
// id: number;
// name: string;
// }
実用例: API レスポンスの型
// 一覧APIでは最小限の情報だけ返す
type UserListItem = Pick<User, "id" | "name" | "email">;
// 詳細APIでは全情報を返す
type UserDetail = User;
function getUsers(): UserListItem[] {
return users.map(({ id, name, email }) => ({ id, name, email }));
}
Omit<T, K> — 特定のプロパティを除外
type UserWithoutId = Omit<User, "id">;
// {
// name: string;
// email: string;
// age: number;
// isActive: boolean;
// }
実用例: 作成APIの引数
// IDはサーバーが自動生成するので除外
type CreateUserInput = Omit<User, "id" | "isActive">;
async function createUser(input: CreateUserInput): Promise<User> {
return await db.users.create({
...input,
id: generateId(),
isActive: true,
});
}
Record<K, V> — キーと値の型を指定したオブジェクト
// 文字列キーと数値の値
type Scores = Record<string, number>;
const scores: Scores = {
math: 90,
english: 85,
science: 92,
};
実用例: ステータス別のラベルマップ
type Status = "draft" | "published" | "archived";
const statusLabels: Record<Status, string> = {
draft: "下書き",
published: "公開中",
archived: "アーカイブ済み",
};
// すべてのステータスに対応するラベルが必須
// "draft" を忘れるとコンパイルエラーになる
実用例: 集計結果
type Category = "food" | "transport" | "entertainment";
const totals: Record<Category, number> = {
food: 50000,
transport: 15000,
entertainment: 20000,
};
Readonly<T> — 全プロパティを読み取り専用に
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1,
name: "田中",
email: "[email protected]",
age: 30,
isActive: true,
};
user.name = "佐藤"; // エラー! 読み取り専用プロパティに代入できない
実用例: イミュータブルな設定
const APP_CONFIG: Readonly<{
apiUrl: string;
timeout: number;
retries: number;
}> = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
Extract<T, U> と Exclude<T, U> — ユニオン型のフィルタリング
type Status = "active" | "inactive" | "pending" | "deleted";
// 特定の型だけ抽出
type ActiveStatus = Extract<Status, "active" | "pending">;
// "active" | "pending"
// 特定の型を除外
type NonDeletedStatus = Exclude<Status, "deleted">;
// "active" | "inactive" | "pending"
実用例: イベント型のフィルタリング
type Event =
| { type: "click"; x: number; y: number }
| { type: "keydown"; key: string }
| { type: "scroll"; offset: number };
type MouseEvent = Extract<Event, { type: "click" }>;
// { type: "click"; x: number; y: number }
NonNullable<T> — null と undefined を除外
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
実用例: API レスポンスの型安全な処理
function processUser(user: User | null | undefined) {
if (!user) throw new Error("User not found");
// この時点で user は NonNullable<User> = User 型
console.log(user.name);
}
ReturnType<T> — 関数の戻り値の型を取得
function getUser() {
return {
id: 1,
name: "田中",
email: "[email protected]",
};
}
type UserFromFunction = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }
実用例: 外部ライブラリの戻り値の型
import { createClient } from "some-library";
type Client = ReturnType<typeof createClient>;
function useClient(client: Client) {
// ...
}
Parameters<T> — 関数の引数の型を取得
function createUser(name: string, age: number, email: string) {
// ...
}
type CreateUserParams = Parameters<typeof createUser>;
// [string, number, string]
type FirstParam = Parameters<typeof createUser>[0];
// string
Awaited<T> — Promise の中身の型を取得
type Result = Awaited<Promise<string>>;
// string
type NestedResult = Awaited<Promise<Promise<number>>>;
// number
実用例: 非同期関数の戻り値
async function fetchUsers() {
const res = await fetch("/api/users");
return res.json() as Promise<User[]>;
}
type Users = Awaited<ReturnType<typeof fetchUsers>>;
// User[]
組み合わせテクニック
Partial + Pick: 一部のフィールドだけオプショナルに
type UserUpdate = Partial<Pick<User, "name" | "email" | "age">>;
// {
// name?: string;
// email?: string;
// age?: number;
// }
Omit + 新しいプロパティ: 型を拡張
type AdminUser = Omit<User, "isActive"> & {
role: "admin" | "superadmin";
permissions: string[];
};
Record + Partial: 柔軟な辞書型
type PartialScores = Partial<Record<string, number>>;
// { [key: string]?: number }
よく使うパターンまとめ
| やりたいこと | ユーティリティ型 |
|---|---|
| 全フィールドをオプショナルに | Partial<T> |
| 全フィールドを必須に | Required<T> |
| 特定フィールドだけ抽出 | Pick<T, K> |
| 特定フィールドを除外 | Omit<T, K> |
| キーと値の型でオブジェクト | Record<K, V> |
| 読み取り専用に | Readonly<T> |
| ユニオン型からフィルタ | Extract<T, U> / Exclude<T, U> |
| null/undefinedを除外 | NonNullable<T> |
| 関数の戻り値の型 | ReturnType<T> |
| 関数の引数の型 | Parameters<T> |
| Promiseの中身の型 | Awaited<T> |
まとめ
ユーティリティ型を活用することで、型定義の重複を減らし、変更に強いコードが書けます。特に Partial、Pick、Omit、Record は日常的に使う場面が多いので、まずはこの4つから覚えていきましょう。
TypeScriptの型定義やJSONスキーマを確認する際は、AssistyのJSONフォーマッターでデータ構造を見やすく整形できます。