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月23日TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理
article2026年1月23日TypeScript 5.8の型推論を比較・検証する 強化点と落とし穴の回避策
article2026年1月23日TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
articleshadcn/ui × TanStack Table 設計術:仮想化・列リサイズ・アクセシブルなグリッド
articleRemix のデータ境界設計:Loader・Action とクライアントコードの責務分離
articlePreact コンポーネント設計 7 原則:再レンダリング最小化の分割と型付け
articlePHP 8.3 の新機能まとめ:readonly クラス・型強化・性能改善を一気に理解
articleNotebookLM に PDF/Google ドキュメント/URL を取り込む手順と最適化
articlePlaywright 並列実行設計:shard/grep/fixtures で高速化するテストスイート設計術
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 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
