Pick / Omit / Partial の実践的な組み合わせ方
Pick、Omit、Partial は TypeScript の代表的なユーティリティ型だ。単体で使うことも多いが、組み合わせるとより柔軟な型定義ができる。実際のユースケースに沿って使い方を見ていこう。
基本のおさらい
まずは各ユーティリティ型の役割を確認する。
type User = {
id: string;
name: string;
email: string;
createdAt: Date;
};
// Pick: 指定したプロパティだけを抽出
type UserName = Pick<User, "name">;
// { name: string }
// Omit: 指定したプロパティを除外
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; createdAt: Date }
// Partial: すべてのプロパティを省略可能に
type PartialUser = Partial<User>;
// { id?: string; name?: string; email?: string; createdAt?: Date }元の型から指定したプロパティだけを取り出す。「これだけ欲しい」に使う。
元の型から指定したプロパティを除外する。「これだけ要らない」に使う。
組み合わせパターン 1:部分更新の型
データベースの更新処理では、一部のフィールドだけを更新することが多い。
type User = {
id: string;
name: string;
email: string;
createdAt: Date;
};
// id と createdAt は更新不可、残りは省略可能
type UpdateUserInput = Partial<Omit<User, "id" | "createdAt">>;
// { name?: string; email?: string }Omit で更新不可のフィールドを除外し、Partial で残りを省略可能にする。API の更新エンドポイントでよく使うパターンだ。
組み合わせパターン 2:作成時の型
新規作成時は id や createdAt がまだ存在しない。
type CreateUserInput = Omit<User, "id" | "createdAt">;
// { name: string; email: string }作成時には必須だが、更新時には省略可能にしたい場合はこう書く。
type CreateUserInput = Omit<User, "id" | "createdAt">;
type UpdateUserInput = Partial<CreateUserInput>;組み合わせパターン 3:一部だけ必須にする
Partial で全部省略可能にした後、特定のプロパティだけ必須に戻したいことがある。
type User = {
id: string;
name: string;
email: string;
};
// すべて省略可能だが、id だけは必須
type UpdateUserInput = Partial<User> & Pick<User, "id">;
// { id: string; name?: string; email?: string }&(交差型)で Pick した型を合成することで、id だけ必須の型が作れる。
組み合わせパターン 4:Required と Partial の併用
Required は Partial の逆で、すべてのプロパティを必須にする。
type Config = {
host?: string;
port?: number;
timeout?: number;
};
// すべて必須に
type RequiredConfig = Required<Config>;
// { host: string; port: number; timeout: number }
// 一部だけ必須に
type PartialConfig = Partial<Config> & Required<Pick<Config, "host">>;
// { host: string; port?: number; timeout?: number }実践例:フォームの型定義
フォームでは、表示用・入力用・送信用で型が微妙に異なることが多い。
// ベースとなる型
type Article = {
id: string;
title: string;
body: string;
authorId: string;
publishedAt: Date | null;
createdAt: Date;
updatedAt: Date;
};
// 新規作成フォーム(id, createdAt, updatedAt は自動生成)
type CreateArticleForm = Omit<Article, "id" | "createdAt" | "updatedAt">;
// 編集フォーム(id は必須、残りは部分更新可能)
type EditArticleForm = Pick<Article, "id"> &
Partial<Omit<Article, "id" | "createdAt" | "updatedAt">>;
// 一覧表示用(body は不要)
type ArticleListItem = Omit<Article, "body">;ベースとなる型を一つ定義し、そこから派生させることで一貫性を保てる。フィールドが追加されたときも、ベースを変更すれば自動的に反映される。
組み合わせが複雑になりすぎると読みにくくなる。型エイリアスで名前を付けたり、コメントを添えるなどの工夫が必要。
注意点:ネストしたオブジェクト
Partial はシャロー(浅い)にしか効かない。
type User = {
id: string;
profile: {
name: string;
age: number;
};
};
type PartialUser = Partial<User>;
// { id?: string; profile?: { name: string; age: number } }
// profile の中身は必須のままネストしたオブジェクトも省略可能にしたい場合は、再帰的な DeepPartial を自作するか、ライブラリを使う必要がある。
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};ユーティリティ型の組み合わせは、一度覚えると型定義の幅が大きく広がる。まずは基本パターンを押さえ、プロジェクトに合わせてカスタマイズしていこう。