TypeScriptユーティリティ型の使い分け: Pick/Omit/Record/Partialを実例で
Pick・Omit・Partial・Record・ReturnType・Awaitedをいつ何に使うか、コピペで動く実例と落とし穴で整理。Claude Codeに型を任せる前に押さえたい基準。
User という型を1つ作ったあと、画面表示用に同じ形をもう一度書き、フォーム入力用にまた書き、API更新用にもう一度書く。気づけば似た型が4つ並んでいて、フィールドを1個足すたびに4箇所を直すハメになる。
僕は最初これを全部手書きしていました。そしてある日、email を必須にし忘れた更新用の型のせいで、空メールのユーザーが本番DBに入りました。型はあるのに事故った。原因は単純で、「元の型から少しだけ変えた型」を毎回コピペで作っていたからです。
これを安全にやる道具が、TypeScriptのユーティリティ型です。Pick や Omit や Partial のことですね。元の型を材料にして、用途別の型を機械的に作る。Claude Codeに型を書かせるときも、この道具の意味を僕ら側が分かっていないと、生成された型が正しいのかレビューできません。
この記事では、よく使う8つを「いつ何に使うか」で並べます。組み合わせ(Partial<Omit<...>> みたいなやつ)や、初心者が必ず踏む落とし穴も、失敗例つきで書きます。
この記事の要点
- ユーティリティ型は「元の型をコピーして少し変える」を安全にやる道具。手書きのコピペをやめると、フィールド追加で型がズレなくなる。
- 表示を絞るなら
Pick、危険な項目だけ消すならOmit、入力の途中ならPartial、決まったキーの辞書はRecord—— 用途で選ぶ。 - 最強の組み合わせは
Awaited<ReturnType<typeof fn>>。API関数の戻り値から画面側の型を自動で導けて、二重管理が消える。 - 最大の落とし穴は「
Omitは型からキーを消すだけで、実行時の値は消えない」。秘密情報の除外は別途コードが要る。 - Claude Codeには「何を残し・何を消し・いつ必須か」を分けて指示すると、
anyで逃げる生成が減る。
ユーティリティ型は「コピーして少し変える」道具
難しく考えなくて大丈夫です。Excelで元のシートをコピーして、いらない列を消したり、必須だった列を空欄OKに変えたりする。あの感覚をTypeScriptの型でやるのがユーティリティ型です。違うのは、その変換を実行前にチェックしてくれる点だけ。
たとえば Pick<User, "id" | "name"> は、User から id と name だけを選んだ新しい型です。逆に Omit<User, "passwordHash"> は、公開したくない passwordHash だけを除いた型。この2つは似て見えますが、発想が真逆です。
僕の使い分けの基準はこうです。残す項目が少ないなら Pick、ほぼ全部残すけど危険な1〜2個だけ消したいなら Omit。一覧画面のカード表示なら表示項目は数個だから Pick。API入力で「id と内部用タイムスタンプだけ要らない」なら Omit。逆を選ぶと、キーの羅列が長くなって読みにくくなります。
Partial<User> は全プロパティを「あってもなくてもいい」に変えます。フォームの下書きやPATCH更新では便利。ただし、新規作成で本当は必須の email まで任意になる、という罠があります。Required<User> はその逆で、任意項目を必須に戻す。保存直前の「もう全部そろってるはず」のデータに使います。Readonly<User> は再代入を禁じる読み取り専用。設定値やマスターデータを途中で書き換えない、という意図を型に残せます。
Record<Keys, Type> は「キーが決まっている辞書」を作る道具。プラン別の機能表やロール別の権限みたいに、キーの顔ぶれが決まっているものに効きます。ReturnType<typeof fn> は関数の戻り値の型を取り出し、Awaited<Promise<T>> は await した後の中身の型を取り出す。後半でこの2つを組み合わせると、API関数からUI側の型が自動で生えてきます。
横断的な型の進め方はClaude CodeでTypeScript開発を速く安全にする実践Tips、型引数や extends 制約などジェネリクスの基礎はTypeScriptジェネリクス入門にまとめています。ユーティリティ型の中身はジェネリクスで出来ているので、仕組みまで知りたいなら後者が近道です。
公式の一覧は TypeScript Handbook の Utility Types が基準です。挙動に迷ったら、まずここを開いてください。
8つを一覧で比べる
走り読みで選べるように、表にしておきます。「迷ったらこの列を見る」のは「実務で使う場面」と「注意点」です。
| 型 | 何をするか | 実務で使う場面 | 注意点 |
|---|---|---|---|
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関数の結果型 | 通信を待つ機能ではない(型だけ) |
この表をそのままClaude Codeに貼って「この基準で既存の型を見直して」と頼むと、効きます。指針がないと生成AIは any で逃げがちですが、判断基準を渡すと選択がブレにくくなる。とくに strict: true のプロジェクトでは曖昧な型がすぐエラーになるので、意図を明文化しておく価値があります。
僕が普段使っている tsconfig.json の型チェック部分はこれです。noUncheckedIndexedAccess を入れておくと、Record から取り出した値が undefined かもしれない事実を型が教えてくれます。
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true
}
}
使いどころ1: 1つの型から画面・下書き・作成入力を作る
管理画面でいちばん多いのが、ユーザー型です。DBに保存する形、外部に見せる形、フォーム入力の形。これを別々に手書きすると、必ずどこかでズレます。だから元の 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;
}
// 公開表示用: 見せたい4項目だけ選ぶ(残す項目が少ないのでPick)
type PublicUser = Pick<User, "id" | "name" | "role" | "bio">;
// フォーム下書き用: 内部項目を消してから全部を任意に(Partial<Omit<...>>の組み合わせ)
type UserDraft = Partial<Omit<User, "id" | "passwordHash" | "createdAt" | "updatedAt">>;
// 新規作成API用: name/email/roleは必須、bioだけ任意
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: "メール確認前の下書き",
};
console.log(publicUser);
console.log(buildCreatePayload({ name: "Aki", email: "[email protected]", role: "editor" }));
console.log(draft);
注目してほしいのは UserDraft の Partial<Omit<User, ...>> です。先に Omit で内部項目(id やタイムスタンプ)を消し、そのうえで Partial で残りを全部任意にしている。順番に意味があって、内側から外側へ「消す → 緩める」と読みます。組み合わせ型は、この入れ子の向きさえ掴めば怖くありません。
Claude Codeに頼むときは、こう分けて書くと生成が安定します。
User型を元に、公開表示用・フォーム下書き用・新規作成API用の3つの型を作ってください。
- 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";
// PlanごとにFeatureの可否を持つ。1つでも埋め忘れると型エラーになる
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")); // true
console.log(canUse("free", "prioritySupport")); // false
Record<Plan, ...> のキーをユニオン型 Plan にしている点が肝です。仮にここを Record<string, ...> にすると、どんな文字列もキーになれてしまい、「埋め忘れを防ぐ」という Record 最大の利点が消えます。候補が決まっているなら、必ずユニオン型をキーにしてください。
Readonly を二重にかけているのは、この設定表を後から書き換えない意図を型に刻むためです。ただし Readonly は基本「浅い」読み取り専用なので、ネストした内側も守りたければ、この例のように内側にも重ねるか、as const を使います。
使いどころ3: API関数の戻り値をそのまま画面の型にする
ここが個人的に「TypeScriptで一番おいしい」と思う組み合わせです。APIクライアントと画面コンポーネントの型を別々に書くと、レスポンスの形を変えたとき片方を直し忘れます。ReturnType と Awaited を重ねると、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"),
};
}
// 関数の戻り値(Promise)の中身を型として取り出す
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)); // inv_20260602: 48,000 JPY (paid)
}
main();
読む順番は内側からです。typeof fetchInvoice で関数の型を取り、ReturnType で戻り値(Promise)を取り、Awaited でその中身を取る。これで fetchInvoice の返り値を変えれば Invoice も InvoiceSummary も自動で追従します。レスポンスに項目を足したとき、画面側で直すべき箇所がコンパイラ任せで見つかる。
Claude CodeにAPI周りを直させるなら、「レスポンス型を別ファイルに手書きしないで、関数から導出して」と一言添えると効きます。ただし注意。Awaited はあくまで型の世界で中身を表すだけで、実行時に通信を待つのは await の仕事です。外部APIやユーザー入力みたいに実行時に壊れうる境界では、型導出だけで安心せず、検証処理も置いてください。
使いどころ4: PATCH入力でPartialの「浅さ」を補う
Partial<T> は便利ですが、ネストしたオブジェクトの中身までは任意にしてくれません。ここは初心者がほぼ全員つまずくので、失敗例と修正版をセットで置きます。
interface Profile {
id: string;
displayName: string;
settings: {
emailNotification: boolean;
smsNotification: boolean;
};
}
// settingsを一旦除外し、settingsだけ「中身も任意」に作り直す
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 },
};
// smsだけ更新できる(emailを再指定しなくてよい)
console.log(patchProfile(profile, { settings: { smsNotification: true } }));
もし ProfilePatch = Partial<Profile> で済ませると、settings を更新するとき emailNotification まで一緒に要求されます(Partial は settings を「あってもなくてもいい」にはするが、ある場合は中身フル指定を求めるから)。深い更新をしたい階層だけ Partial<Profile["settings"]> に切り出すと読みやすい。汎用の深いReadonly/Partial型を自作したくなる場面ですが、チームの初心者が読むコードでは、まず具体的な型を優先したほうが保守はラクです。
ここで必ず転ぶ落とし穴
いちばん多いのが Omit の誤解です。Omit は型からキーを消すだけで、実行時のオブジェクトからプロパティを削除しません。 秘密情報の除外には、ちゃんとコードが要ります。
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: "[email protected]",
passwordHash: "secret",
}));
SafeAccount 型を返り値に書いただけで安心して、return account とそのまま返すと、型チェックは通るのに passwordHash がレスポンスに乗ります。これは事故です。型と実体は別、と覚えてください。
残り3つも短く。Required<T> は「保存前に全部そろった型」を表せますが、任意項目まで無理に必須化するとフォームが不自然になる。保存直前で検証済みデータに変換する境界を決めて使う。Record<string, T> はキーが何でもよくなるので設定漏れを防ぐ力が弱い。候補が決まっているならユニオン型キーにする。Awaited<T> は型だけの話で、ローディングやエラー処理は別途必要 —— ここを曖昧にすると、Claude Codeが型だけ整えて待ち処理を置き忘れます。
Claude Codeに型をレビューさせる依頼文
実装後は、「型を短くして」ではなく事故防止の観点でレビューさせます。僕がそのまま使っているプロンプトです。
このTypeScriptの型設計をレビューしてください。
1. Pick/Omit/Partial/Required/Readonly/Recordの選択が用途と合っているか
2. Omitで消したつもりの秘密情報が実行時にも消えているか
3. Partialが新規作成の必須項目を緩めすぎていないか
4. ReturnType/AwaitedでAPI型の二重管理を減らせているか
5. strict設定で壊れる曖昧なanyやstringが残っていないか
僕も最初は Partial で全部を緩めすぎて、保存時に空の email を許す設計にしてしまいました。冒頭の事故はこれです。その後「下書き」「作成」「保存済み」を別の型に分けてから、Claude Codeの修正提案もレビューしやすくなりました。
よくある質問
Q. PickとOmit、結局どっちを使えばいい?
残す項目が少ないなら Pick、ほぼ全部残して危険な1〜2個だけ消すなら Omit。キーの羅列が短くなるほうを選ぶと読みやすくなります。
Q. Recordのキーは string でいい?
候補が決まっているなら避けてください。type Plan = "free" | "pro" | "team" のユニオン型をキーにすると、埋め忘れや綴りミスをコンパイル時に弾けます。string だと何でも入ってしまい、チェックが効きません。
Q. Partial<Omit<User, "id">> みたいな入れ子はどう読む?
内側から外側です。先に Omit で id を消し、その結果を Partial で全部任意にする、と読みます。「消す → 緩める」の順番。
Q. Omit で消した秘密情報がレスポンスに出てしまいます。
Omit は型からキーを消すだけで実体は消しません。const { passwordHash, ...rest } = obj のように分割代入で実体からも除外してください。
Q. DeepPartial のような深い汎用型は自作すべき?
チームに初心者がいるなら、まず更新したい階層だけ具体的に書くほうが保守はラクです。汎用型はエラーメッセージが読みにくくなりがちなので、必要になってから導入します。
実際に試した結果
この記事のコードは、strict: true・noUncheckedIndexedAccess: true の設定でも構文エラーなく動くのを確認しました。とくに効いたのは Awaited<ReturnType<typeof fetchInvoice>> で、レスポンスに項目を足したとき、画面側の直すべき箇所をコンパイラが片っ端から教えてくれます。型の二重管理が消えるのは想像以上に快適でした。
逆に、Omit は何度試しても実行時の値を消してくれません。当たり前なんですが、公開レスポンスを返す関数では「分割代入で実体からも除外する」を運用ルールにしないと、いつか秘密情報を漏らします。型を信じすぎないこと —— これが僕がこのテーマで払った授業料の結論です。
ユーティリティ型は型芸ではなく、同じ型を何度も手書きしないための実務道具です。Pick/Omit で見せる項目を制御し、Partial/Required で入力の段階を分け、Readonly/Record で設定の抜け漏れを防ぎ、ReturnType/Awaited でAPI型の重複を消す。まずは使いどころ1の User の例を自分のプロジェクトに置き換えてみてください。
「型はあるのに事故が減らない」「AIに直させると型が崩れる」と感じているなら、Claude Code導入相談から声をかけてください。実際の型設計を一緒に見直します。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeに1ファイルだけ直させる指示文のつくり方
「もっと良くして」で40行も変えられた失敗から学んだ、触る範囲・検証・戻し方をセットにしたClaude Code用の依頼文テンプレートを紹介します。
Claude Code の権限拒否から復旧する: 止まった理由を次の安全手順に変える
Claude Code のコマンドが拒否されたとき、焦って許可を広げずに、拒否理由、代替手順、証拠コマンド、再試行条件へ分解する方法。
Claude Codeにビルド→スモークテスト→自動修正を回させる足場の作り方
最小スモークテストの選び方、失敗ログを食わせて直させるループ、回数上限と確認ゲートで暴走を止める方法を、コピペで動くコード付きで紹介します。