TypeScriptのユーティリティ型を早見表で使いこなす Partial Pick Omitの実践活用
TypeScript で型安全な開発を進めるなかで、「同じような型を何度も定義している」「API のリクエストとレスポンスで微妙に異なる型が必要」といった悩みを抱えていないでしょうか。この記事では、Utility Types(ユーティリティ型)の中でも実務で使用頻度の高い Partial・Pick・Omit を中心に、チートシート形式で整理しながら、型設計の判断基準を解説します。実際に業務で採用・不採用を決めた経験をもとに、Mapped Types の仕組みから実践的な組み合わせパターンまで、初学者から実務者まで役立つ内容をまとめました。
TypeScript Utility Types 早見表
まず、よく使う Utility Types を用途別に整理した早見表を示します。
| 型名 | 用途 | 変換内容 | 主な使用場面 |
|---|---|---|---|
Partial<T> | 全プロパティをオプショナル化 | { a: string } → { a?: string } | フォーム入力、PATCH API |
Required<T> | 全プロパティを必須化 | { a?: string } → { a: string } | バリデーション後の型 |
Pick<T, K> | 指定プロパティのみ抽出 | { a, b, c } → { a, b } | API レスポンス、表示用型 |
Omit<T, K> | 指定プロパティを除外 | { a, b, c } → { c } | 新規作成 API(id 除外) |
Readonly<T> | 全プロパティを読み取り専用化 | { a: string } → { readonly a: string } | 設定値、定数オブジェクト |
Record<K, V> | キーと値の型を指定した辞書型 | Record<string, number> | マップ構造、集計データ |
Exclude<T, U> | ユニオン型から特定の型を除外 | 'a' | 'b' | 'c' → 'a' | 'b' | 状態遷移、enum 風の絞り込み |
Extract<T, U> | ユニオン型から特定の型を抽出 | 'a' | 'b' | 'c' → 'c' | 共通部分の抽出 |
NonNullable<T> | null・undefined を除外 | string | null → string | null 安全な処理 |
ReturnType<T> | 関数の戻り値型を取得 | () => string → string | 既存関数との型整合 |
Parameters<T> | 関数の引数型をタプルで取得 | (a: string, b: number) => void → [string, number] | ラッパー関数の作成 |
それぞれの詳細な使い方と判断基準は後述します。
検証環境
- OS: macOS Sequoia 15.2
- Node.js: 22.13.0
- TypeScript: 5.7.3
- 主要パッケージ:
- @types/node: 22.10.7
- 検証日: 2026 年 01 月 21 日
Utility Types が必要になる背景
TypeScript による型安全な開発では、同じデータ構造に対して「少しだけ異なる型」が必要になる場面が頻繁に発生します。
mermaidflowchart LR
base["User 型<br/>id, name, email"] --> create["作成用<br/>id なし"]
base --> update["更新用<br/>全て optional"]
base --> display["表示用<br/>name, email のみ"]
上図のように、1 つの基本型から複数のバリエーションが派生するケースは珍しくありません。
たとえば、ユーザー情報を扱うシステムでは以下のような型が必要になります。
- 新規作成時:
idは自動採番されるため不要 - 更新時: 変更するフィールドだけを送信するため、すべてオプショナル
- 一覧表示時:
emailは個人情報なので含めない
これらを個別に定義すると、以下のような問題が起きます。
typescript// ❌ 問題のある実装:同じ構造を複数回定義
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserRequest {
name: string;
email: string;
}
interface UpdateUserRequest {
name?: string;
email?: string;
}
interface UserListItem {
id: number;
name: string;
}
実際に検証したところ、この方法では User 型にプロパティを追加したとき、関連する 3 つの型すべてを手動で修正する必要がありました。プロパティが 10 個以上ある型では修正漏れが頻発し、型の不整合によるランタイムエラーにつながった経験があります。
つまずきやすい点: 型定義の重複は一見問題なく動作するため、プロジェクトが大きくなるまで気づきにくい。
型定義の重複が引き起こす課題
型定義を重複させることで発生する問題を整理します。
保守性の低下
基本となる型に変更が入ると、派生型すべてを手動で修正する必要があります。業務で問題になったのは、プロパティ名の変更(name → displayName)を行った際に、派生型の一部で古い名前が残り、コンパイルは通るものの実行時に undefined が返るバグが発生したケースです。
型の不整合
コピー&ペーストで型を作成すると、元の型と派生型の間で微妙な違いが生まれます。
typescript// 元の型
interface User {
id: number;
name: string;
email: string;
createdAt: Date; // 後から追加
}
// 派生型(更新漏れ)
interface CreateUserRequest {
name: string;
email: string;
// createdAt が漏れている
}
認知負荷の増加
型定義が増えるほど、「この場面ではどの型を使うべきか」の判断が難しくなります。実務では、チームメンバーが独自の派生型を作成し、同じ目的の型が複数存在する状態になったことがあります。
mermaidflowchart TD
problem["型定義の重複"] --> maintain["保守性の低下"]
problem --> inconsist["型の不整合"]
problem --> cognitive["認知負荷の増加"]
maintain --> bug["修正漏れによるバグ"]
inconsist --> runtime["ランタイムエラー"]
cognitive --> duplicate["重複型の乱立"]
これらの課題を解決するのが Utility Types です。Mapped Types の仕組みを活用し、基本型から派生型を自動生成することで、型の一貫性を保ちながら保守性を高められます。
Partial・Pick・Omit の使い分けと判断基準
ここでは、実務で最も使用頻度の高い 3 つの Utility Types について、採用・不採用の判断基準を解説します。
Partial:全プロパティをオプショナル化
Partial<T> は、型 T のすべてのプロパティをオプショナル(省略可能)にします。
typescriptinterface User {
id: number;
name: string;
email: string;
}
// 全プロパティがオプショナルになる
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }
採用すべき場面
- PATCH API のリクエスト型: 更新したいフィールドだけを送信する
- フォームの入力途中状態: すべての項目が埋まっていない状態を許容する
- デフォルト値とのマージ: 一部だけ上書きしたい設定オブジェクト
以下は動作確認済みのコード例です。
typescript// PATCH API のリクエスト型として使用
function updateUser(id: number, updates: Partial<Omit<User, "id">>) {
// updates は { name?: string; email?: string }
console.log(`Updating user ${id}:`, updates);
}
updateUser(1, { name: "新しい名前" }); // OK
updateUser(1, {}); // OK(何も更新しない)
採用しなかった場面
検証の結果、以下のケースでは Partial の採用を見送りました。
- 必須項目が明確に存在する場合: たとえば
idは必須だが他はオプショナルという型では、Partialだけでは表現できない - ネストしたオブジェクトを含む場合:
Partialは浅い(shallow)変換のため、ネストした部分はオプショナルにならない
typescriptinterface NestedUser {
id: number;
profile: {
name: string;
bio: string;
};
}
type PartialNestedUser = Partial<NestedUser>;
// profile 自体はオプショナルになるが、
// profile.name や profile.bio は必須のまま
const user: PartialNestedUser = {
profile: { name: "テスト" }, // ❌ bio が必須なのでエラー
};
つまずきやすい点:
Partialはネストしたオブジェクトの中身までオプショナルにしない。深い階層まで対応するにはDeepPartialを自作する必要がある。
Pick:指定プロパティのみ抽出
Pick<T, K> は、型 T から指定したプロパティ K だけを抽出します。
typescript// name と email だけを抽出
type UserProfile = Pick<User, "name" | "email">;
// { name: string; email: string; }
採用すべき場面
- API レスポンスの絞り込み: 必要なフィールドだけを返す
- 表示用コンポーネントの Props: 表示に必要な情報だけを受け取る
- セキュリティ上の理由で一部だけ公開: 内部用の型から公開用の型を生成
typescript// 一覧表示用(email は個人情報なので含めない)
type UserListItem = Pick<User, 'id' | 'name'>;
// コンポーネントの Props として使用
function UserCard({ id, name }: UserListItem) {
return <div>{name}</div>;
}
採用しなかった場面
- 除外したいプロパティが少ない場合: 10 個のプロパティから 8 個を選ぶなら、
Omitで 2 個除外する方が簡潔 - 動的にプロパティを決定する場合:
Pickの第 2 引数は静的な文字列リテラルのユニオン型である必要がある
Omit:指定プロパティを除外
Omit<T, K> は、型 T から指定したプロパティ K を除外します。
typescript// id を除外(新規作成用)
type CreateUserRequest = Omit<User, "id">;
// { name: string; email: string; }
採用すべき場面
- 新規作成 API のリクエスト型:
idやcreatedAtなど自動生成されるフィールドを除外 - 継承元の型から不要なプロパティを除去: 外部ライブラリの型をカスタマイズ
typescript// 新規作成API(id と createdAt は自動生成)
interface FullUser {
id: number;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
type CreateUserRequest = Omit<FullUser, "id" | "createdAt" | "updatedAt">;
// { name: string; email: string; }
Pick と Omit の使い分け判断基準
| 状況 | 推奨する型 | 理由 |
|---|---|---|
| 抽出したいプロパティが少ない | Pick | 列挙が簡潔 |
| 除外したいプロパティが少ない | Omit | 列挙が簡潔 |
| プロパティ数が同程度 | Omit | 新規プロパティ追加時に自動で含まれる |
| セキュリティ上、明示的に許可したい | Pick | ホワイトリスト方式で安全 |
実際に試したところ、Omit は「新しいプロパティが追加されたとき自動で含まれる」という特性があるため、意図しないプロパティが含まれるリスクがあります。機密情報を扱う API では Pick でホワイトリスト方式を採用する方が安全です。
実務で使える組み合わせパターン
Utility Types は単独で使うだけでなく、組み合わせることでより柔軟な型設計が可能です。
パターン 1:Partial と Omit の組み合わせ(PATCH API)
id は必須で、それ以外は任意で更新できる型を作成します。
typescriptinterface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
}
// id 以外をオプショナルに
type UpdateUserRequest = Partial<Omit<User, "id">> & Pick<User, "id">;
function updateUser(request: UpdateUserRequest) {
console.log(`Updating user ${request.id}`);
// request.name, request.email, request.role はすべてオプショナル
}
updateUser({ id: 1, name: "新しい名前" }); // OK
updateUser({ id: 1 }); // OK(何も更新しない)
// updateUser({ name: '名前' }); // ❌ id が必須なのでエラー
パターン 2:Pick と Required の組み合わせ
オプショナルなプロパティを含む型から、特定のプロパティを必須化して抽出します。
typescriptinterface Config {
apiUrl?: string;
timeout?: number;
retryCount?: number;
debug?: boolean;
}
// apiUrl と timeout は必須、他は不要
type RequiredConfig = Required<Pick<Config, "apiUrl" | "timeout">>;
// { apiUrl: string; timeout: number; }
パターン 3:Record と Pick の組み合わせ(辞書型)
特定のキーだけを持つ辞書型を作成します。
typescripttype UserRole = "admin" | "editor" | "viewer";
// 各ロールの権限を定義
type Permissions = Record<
UserRole,
{
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
}
>;
const permissions: Permissions = {
admin: { canRead: true, canWrite: true, canDelete: true },
editor: { canRead: true, canWrite: true, canDelete: false },
viewer: { canRead: true, canWrite: false, canDelete: false },
};
カスタム Utility Types の作成
標準の Utility Types では対応できないケースでは、Mapped Types を活用してカスタム型を作成します。
DeepPartial:ネストしたオブジェクトも再帰的にオプショナル化
typescripttype DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
interface NestedConfig {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
database: {
url: string;
};
}
// すべてのネストしたプロパティがオプショナルに
type PartialConfig = DeepPartial<NestedConfig>;
const config: PartialConfig = {
server: {
ssl: {
enabled: true,
// cert は省略可能
},
},
// database は省略可能
};
PickByType:特定の型を持つプロパティだけを抽出
typescripttype PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface MixedData {
id: number;
name: string;
count: number;
isActive: boolean;
description: string;
}
// number 型のプロパティだけを抽出
type NumberProps = PickByType<MixedData, number>;
// { id: number; count: number; }
// string 型のプロパティだけを抽出
type StringProps = PickByType<MixedData, string>;
// { name: string; description: string; }
Mutable:Readonly を解除
外部ライブラリが返す Readonly な型を変更可能にしたい場合に使用します。
typescripttype Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyUser {
readonly id: number;
readonly name: string;
}
type MutableUser = Mutable<ReadonlyUser>;
// { id: number; name: string; } (readonly が解除される)
Utility Types 詳細比較まとめ
実務での採用判断に役立つよう、詳細な比較表を示します。
| 型名 | 向いているケース | 向かないケース | 注意点 |
|---|---|---|---|
Partial<T> | PATCH API、フォーム入力途中 | 必須項目がある場合、ネストしたオブジェクト | 浅い変換のみ |
Required<T> | バリデーション後、設定の確定 | 一部だけ必須化したい場合 | すべてが必須になる |
Pick<T, K> | 明示的に許可したいプロパティがある | 抽出したいプロパティが多い | ホワイトリスト方式 |
Omit<T, K> | 除外したいプロパティが少ない | 機密情報を含む型 | 新規プロパティが自動で含まれる |
Readonly<T> | 設定値、定数、イミュータブルなデータ | 更新が必要なオブジェクト | 浅い変換のみ |
Record<K, V> | 辞書型、マップ構造 | 動的にキーが増減する場合 | キーの型が固定される |
判断フローチャート
mermaidflowchart TD
start["型の変換が必要"] --> q1{"プロパティを<br/>増やす?減らす?"}
q1 -->|減らす| q2{"多くを残す?<br/>少しだけ残す?"}
q2 -->|多くを残す| omit["Omit を使用"]
q2 -->|少しだけ残す| pick["Pick を使用"]
q1 -->|変更する| q3{"オプショナル?<br/>必須?<br/>読み取り専用?"}
q3 -->|オプショナル| partial["Partial を使用"]
q3 -->|必須| required["Required を使用"]
q3 -->|読み取り専用| readonly["Readonly を使用"]
q1 -->|新しい構造| record["Record を使用"]
上図は型変換の方針を決める際の判断フローです。まず「プロパティを増やすか減らすか」を考え、次に具体的な Utility Types を選択します。
まとめ
TypeScript の Utility Types は、型定義の重複を排除し、型安全な開発を効率化するための重要な機能です。
- Partial は PATCH API やフォームの入力途中状態に適している
- Pick はセキュリティ上、明示的に許可したいプロパティがある場合に有効
- Omit は除外したいプロパティが少ない場合に簡潔に書ける
- 組み合わせることで、より複雑な型変換にも対応できる
- 標準の Utility Types で足りない場合は、Mapped Types を活用してカスタム型を作成する
ただし、Utility Types を過度に組み合わせると可読性が低下するため、チーム内で「どの程度の複雑さまで許容するか」のガイドラインを設けることを推奨します。実際の業務では、3 つ以上の Utility Types を組み合わせる場合は型エイリアスで名前をつけ、意図を明確にするルールを採用しています。
型設計は「正解が 1 つではない」領域です。プロジェクトの規模やチームの習熟度に応じて、適切な抽象度を選択してください。
関連リンク
著書
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
article2026年1月21日TypeScriptのユーティリティ型を早見表で使いこなす Partial Pick Omitの実践活用
article2026年1月21日TypeScriptデコレータを使い方で完全攻略 メタプログラミング設計の要点を整理
article2026年1月21日TailwindとTypeScriptとReactのユースケース 型安全なデザインシステムを設計して構築する
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
article2026年1月21日TypeScriptのユーティリティ型を早見表で使いこなす Partial Pick Omitの実践活用
article2026年1月21日TypeScriptデコレータを使い方で完全攻略 メタプログラミング設計の要点を整理
article2026年1月21日TailwindとTypeScriptとReactのユースケース 型安全なデザインシステムを設計して構築する
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
