Pick / Omit / Partial の実践的な組み合わせ方

PickOmitPartial は 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 }
Pick

元の型から指定したプロパティだけを取り出す。「これだけ欲しい」に使う。

Omit

元の型から指定したプロパティを除外する。「これだけ要らない」に使う。

組み合わせパターン 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:作成時の型

新規作成時は idcreatedAt がまだ存在しない。

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 の併用

RequiredPartial の逆で、すべてのプロパティを必須にする。

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];
};

ユーティリティ型の組み合わせは、一度覚えると型定義の幅が大きく広がる。まずは基本パターンを押さえ、プロジェクトに合わせてカスタマイズしていこう。