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>

まとめ

ユーティリティ型を活用することで、型定義の重複を減らし、変更に強いコードが書けます。特に PartialPickOmitRecord は日常的に使う場面が多いので、まずはこの4つから覚えていきましょう。


TypeScriptの型定義やJSONスキーマを確認する際は、AssistyのJSONフォーマッターでデータ構造を見やすく整形できます。