Tips & Tricks (更新: 2026/6/2)

TypeScriptユーティリティ型入門: Claude Codeで実務の型を安全に作る

Pick/Omit/Partialなどの使い分けを、Claude Codeで動く実例と失敗例から学ぶ実務ガイド。

TypeScriptユーティリティ型入門: Claude Codeで実務の型を安全に作る

TypeScriptのユーティリティ型は、ひと言でいうと「既存の型を材料にして、別の用途向けの型を作る道具」です。 User という元の型から「画面表示用」「入力フォーム用」「API更新用」を毎回手で書くと、フィールド追加のたびにズレます。 Claude Codeに任せる場合も、最初にこの道具の意味を人間が押さえておくと、生成された型をレビューしやすくなります。

この記事では、PickOmitPartialRequiredReadonlyRecordReturnTypeAwaited を初心者向けに整理します。 そのうえで、Claude Codeにどう依頼すると実務の型設計に落とし込めるかを、コピペで動くコードで確認します。 公式仕様は TypeScript HandbookのUtility Types を基準にし、型エラーを早く見つける設定は TSConfigのstrict も参照してください。

関連する基礎として、型全体の進め方はTypeScript開発を速く安全にする実践Tipsを、ジェネリクスの考え方はTypeScriptジェネリクス実践ガイドを先に読むとつながりやすいです。

まず平易に理解する

ユーティリティ型は、型の「コピーして少し変える」を安全に行う仕組みです。 Excelで元シートをコピーして、不要な列を消したり、必須列を任意入力に変えたりする感覚に近いです。 ただしTypeScriptでは、その変換をコンパイル時、つまり実行前のチェックとして扱います。

たとえば Pick<User, "id" | "name">User から idname だけを選びます。 Omit<User, "passwordHash"> は、公開してはいけない passwordHash だけを除外します。 この2つは似ていますが、発想が逆です。 「表示項目が少ない」なら Pick、「ほぼ同じだが危険な項目だけ消す」なら Omit が読みやすくなります。

Partial<User> は全プロパティを任意にします。 フォームの下書きやPATCH APIの入力では便利ですが、「新規作成に必要な email まで任意になる」落とし穴があります。 逆に Required<User> は任意プロパティを必須に戻します。 Readonly<User> は再代入を防ぐ読み取り専用の型で、設定やマスターデータの事故防止に向きます。

Record<Keys, Type> は「決まったキーの辞書」を作ります。 ReturnType<typeof fn> は関数の戻り値の型を取り出します。 Awaited<Promise<T>>await 後の中身の型を取り出します。 この2つを組み合わせると、API関数の戻り値から画面側の型を自動で作れます。

flowchart LR
  A["元の型: User"] --> B["Pick: 公開表示"]
  A --> C["Omit: 秘密項目を除外"]
  A --> D["Partial: 更新入力"]
  A --> E["Required: 必須入力"]
  A --> F["Readonly: 設定固定"]
  G["関数"] --> H["ReturnType"]
  I["Promise"] --> J["Awaited"]

主要ユーティリティ型の比較

何をするか実務で使う場面注意点
Pick<T, K>必要なキーだけ選ぶ一覧、公開プロフィール、カード表示選んでいないキーは後から使えない
Omit<T, K>指定キーだけ除外する作成入力、外部公開、ログ出力実行時の値からは消えない
Partial<T>全キーを任意にする下書き、PATCH、フォーム途中状態ネストした中身までは任意にならない
Required<T>全キーを必須にする保存直前の検証済みデータ本当に全キーが必要か確認する
Readonly<T>再代入を禁止する設定、権限定義、固定マスターオブジェクトの深い階層は別途対策が必要
Record<K, T>キーが決まった辞書を作るロール別権限、文言、価格表string をキーにすると広すぎる
ReturnType<T>関数の戻り値を取り出すAPI関数とUI型の同期typeof 関数名 と書く
Awaited<T>Promiseの中身を取り出すasync関数の結果型実行時にPromiseを解決する機能ではない

この表をClaude Codeに貼り、「この方針で型を見直して」と依頼すると、any で逃げる生成を減らせます。 特に strict: true のプロジェクトでは、曖昧な型がすぐエラーになるため、ユーティリティ型の意図を明文化する価値があります。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "skipLibCheck": true
  }
}

使いどころ1: User型から画面とフォームを作る

最初の例は、管理画面でよくあるユーザー型です。 データベースに保存する型、外部に見せる型、フォーム入力の型を同じ形で書き写すと、いつかズレます。 そこで元の User を1つだけ持ち、用途ごとにユーティリティ型で変換します。

type UserRole = "admin" | "editor" | "viewer";

interface User {
  id: string;
  name: string;
  email: string;
  role: UserRole;
  bio: string;
  passwordHash: string;
  createdAt: Date;
  updatedAt: Date;
}

type PublicUser = Pick<User, "id" | "name" | "role" | "bio">;
type UserDraft = Partial<Omit<User, "id" | "passwordHash" | "createdAt" | "updatedAt">>;

type CreateUserInput =
  Required<Pick<User, "name" | "email" | "role">> &
  Partial<Pick<User, "bio">>;

function buildCreatePayload(input: CreateUserInput): Omit<User, "id" | "createdAt" | "updatedAt"> {
  return {
    name: input.name,
    email: input.email,
    role: input.role,
    bio: input.bio ?? "",
    passwordHash: "hashed-by-server",
  };
}

const publicUser: PublicUser = {
  id: "u_001",
  name: "Masa",
  role: "admin",
  bio: "Claude Code workflow designer",
};

const draft: UserDraft = {
  name: "Draft user",
  bio: "Saved before email is confirmed",
};

console.log(publicUser);
console.log(buildCreatePayload({ name: "Aki", email: "aki@example.com", role: "editor" }));
console.log(draft);

Claude Codeへの依頼は、次のように「何を残すか」「何を消すか」「いつ必須か」を分けて書くと安定します。

User型を元に、公開表示用、フォーム下書き用、新規作成API用の型を作ってください。
passwordHashは外部公開しないでください。
新規作成ではname、email、roleだけ必須にし、bioは任意にしてください。
Pick/Omit/Partial/Requiredを使い、なぜその型を選んだか短くコメントしてください。

実務では、ここにZodなどの実行時バリデーションを組み合わせます。 ユーティリティ型は「コードを書く前に形をチェックする道具」であり、フォーム送信値の実体を検証するものではありません。

使いどころ2: プラン別機能と権限をRecordで固定する

2つ目は、料金プランやロール別権限のような「キーが決まっている表」です。 Record を使うと、team プランだけ設定し忘れる、prioritySupport の綴りを間違える、といったミスをコンパイル時に見つけやすくなります。

type Plan = "free" | "pro" | "team";
type Feature = "exportPdf" | "inviteMember" | "prioritySupport";

const featureMatrix: Readonly<Record<Plan, Readonly<Record<Feature, boolean>>>> = {
  free: {
    exportPdf: false,
    inviteMember: false,
    prioritySupport: false,
  },
  pro: {
    exportPdf: true,
    inviteMember: false,
    prioritySupport: false,
  },
  team: {
    exportPdf: true,
    inviteMember: true,
    prioritySupport: true,
  },
};

function canUse(plan: Plan, feature: Feature): boolean {
  return featureMatrix[plan][feature];
}

console.log(canUse("pro", "exportPdf"));
console.log(canUse("free", "prioritySupport"));

ここで Readonly を付けているのは、設定表を途中で書き換えない意図を型に残すためです。 ただし Readonly は基本的に浅い読み取り専用です。 ネストしたオブジェクトも守りたい場合は、この例のように内側にも Readonly を重ねるか、as const や専用の深いReadonly型を検討します。

使いどころ3: API関数の戻り値をReturnTypeとAwaitedで使い回す

APIクライアントと画面コンポーネントの型を別々に書くと、レスポンス変更に弱くなります。 ReturnTypeAwaited を組み合わせると、async関数の結果から画面側の型を取り出せます。

async function fetchInvoice(invoiceId: string) {
  return {
    id: invoiceId,
    status: "paid" as const,
    amount: 48000,
    currency: "JPY" as const,
    paidAt: new Date("2026-06-02T10:00:00+09:00"),
  };
}

type Invoice = Awaited<ReturnType<typeof fetchInvoice>>;
type InvoiceSummary = Pick<Invoice, "id" | "status" | "amount" | "currency">;

function formatInvoice(invoice: InvoiceSummary): string {
  return `${invoice.id}: ${invoice.amount.toLocaleString()} ${invoice.currency} (${invoice.status})`;
}

async function main() {
  const invoice = await fetchInvoice("inv_20260602");
  console.log(formatInvoice(invoice));
}

main();

Claude CodeにAPI周辺を直してもらうときは、「レスポンス型を別ファイルに手書きしないで、関数から導出して」と明示すると効果があります。 関数が信頼できる境界なら、型の二重管理を減らせます。 逆に、外部APIやユーザー入力のように実行時に壊れうる境界では、型導出だけでなく検証処理も必要です。

使いどころ4: PATCH入力でPartialの浅さを補う

Partial<T> は便利ですが、ネストしたオブジェクトの中身までは任意にしません。 初心者がつまずきやすい点なので、失敗例と修正版をセットで見ます。

interface Profile {
  id: string;
  displayName: string;
  settings: {
    emailNotification: boolean;
    smsNotification: boolean;
  };
}

type ProfilePatch =
  Omit<Partial<Profile>, "settings"> & {
    settings?: Partial<Profile["settings"]>;
  };

function patchProfile(current: Profile, patch: ProfilePatch): Profile {
  return {
    ...current,
    ...patch,
    settings: {
      ...current.settings,
      ...patch.settings,
    },
  };
}

const profile: Profile = {
  id: "p_001",
  displayName: "Masa",
  settings: {
    emailNotification: true,
    smsNotification: false,
  },
};

console.log(patchProfile(profile, { settings: { smsNotification: true } }));

ここで ProfilePatch = Partial<Profile> だけにすると、settings を更新するときに emailNotification も要求されます。 深い更新をしたい場合は、対象の階層だけ Partial<Profile["settings"]> に分けるほうが読みやすいです。 深い汎用型を作りたくなりますが、チームの初心者が読むコードでは、まず具体的な型を優先したほうが保守しやすいことが多いです。

具体的な落とし穴

Omit は型からキーを消すだけで、実行時のオブジェクトからプロパティを削除しません。 ログやAPIレスポンスから秘密情報を取り除く処理は、別途実装する必要があります。

interface Account {
  id: string;
  email: string;
  passwordHash: string;
}

type SafeAccount = Omit<Account, "passwordHash">;

function toSafeAccount(account: Account): SafeAccount {
  const { passwordHash, ...safeAccount } = account;
  return safeAccount;
}

console.log(toSafeAccount({
  id: "a_001",
  email: "masa@example.com",
  passwordHash: "secret",
}));

Record<string, T> は便利そうに見えますが、キーが何でもよくなるため、設定漏れを防ぐ力が弱くなります。 料金プラン、ロール、ステータスのように候補が決まっているなら、type Plan = "free" | "pro" | "team" のようなユニオン型をキーにしてください。

Required<T> は「保存前に全部そろった型」を表現できますが、任意項目まで無理に必須にするとフォームが不自然になります。 保存処理の直前で検証済みデータに変換する、という境界を決めて使うのが安全です。

Awaited<T> は型の世界でPromiseの中身を表すだけです。 実行時に通信を待つ処理は await.then() が必要です。 この違いを曖昧にすると、Claude Codeが型だけ整えて、ローディングやエラー処理を置き忘れることがあります。

Claude Codeにレビューさせるチェックリスト

実装後は、Claude Codeに「型をもっと短くして」ではなく、次の観点でレビューさせます。

このTypeScriptの型設計をレビューしてください。
1. Pick/Omit/Partial/Required/Readonly/Recordの選択が用途と合っているか
2. Omitで消したつもりの秘密情報が実行時にも消えているか
3. Partialが新規作成の必須項目を緩めすぎていないか
4. ReturnType/AwaitedでAPI型の二重管理を減らせているか
5. strict設定で壊れる曖昧なanyやstringが残っていないか

この依頼は、型の美しさよりも事故防止に寄せています。 Masaが記事や小さなSaaSの管理画面を作るときも、最初は Partial で全部を緩めすぎ、保存時に空の email を許す設計にしてしまったことがありました。 その後は「下書き」「作成」「保存済み」を別の型に分けるようにして、Claude Codeの修正提案もレビューしやすくなりました。

まとめ

TypeScriptユーティリティ型は、難しい型芸ではありません。 実務では、同じ型を何度も手書きせず、用途ごとの差分を安全に表すための道具です。 PickOmit で見せる項目を制御し、PartialRequired で入力の段階を分け、ReadonlyRecord で設定の抜け漏れを防ぎ、ReturnTypeAwaited でAPI型の重複を減らします。

ClaudeCodeLabでは、Claude Codeを使ったTypeScript設計、記事CMS、社内ツール、収益化導線の実装相談を受けています。 自分のプロジェクトで「型はあるのに事故が減らない」「AIに直させると型が崩れる」と感じている場合は、Claude Code導入相談から相談してください。

この記事で紹介した内容を実際に試した結果、上のコード例は strict 前提でも構文エラーなく確認できました。 特に ReturnTypeAwaited でAPIレスポンス型を導出すると、レスポンス変更時に画面側の修正箇所が早く見つかります。 一方で、Omit は実行時の秘密情報を消さないため、公開レスポンスを返す関数では分割代入による除外処理を必ず残す、という運用ルールが必要でした。

#Claude Code #TypeScript #ユーティリティ型 #型安全 #コード品質
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。