【実務・中級編】 組み込みユーティリティ型(Partial, Required, Pick, Omit) – TypeScript実践ガイド

やあ。現場でTypeScriptと格闘している君へ。

「APIから返ってきた型をそのまま使いたいけど、一部のプロパティだけ必須にしたいんだよな……」「この巨大なインターフェース、一部分だけ切り出せないのか?」――そんな悩みを抱えたことはないだろうか?

TypeScriptを書き始めた頃は、`interface`や`type`をひたすら手書きして、変更のたびに地獄を見る……なんて経験を誰もが一度は通るはずだ。だが、シニアレベルを目指すなら、もう「手書き」という原始的な手法は卒業しよう。

今日は、TypeScriptの標準ライブラリに隠された「型を自在に操る錬金術」、すなわち組み込みユーティリティ型(Partial, Required, Pick, Omit)について、実務の泥臭い話を交えながら深掘りしていく。

—

なぜ「型変換」が必要なのか?

結論から言えば、「DRY原則(Don’t Repeat Yourself)を型レベルで守るため」だ。

例えば、バックエンドから受け取るユーザー情報の型があるとする。

interface User {
id: string;
name: string;
email: string;
age?: number; // ageはオプショナル
}

フロントエンドでは、「更新画面(全項目必要)」「プロフィール編集(一部だけ更新)」「一覧表示(IDと名前だけ欲しい)」など、同じエンティティに対して複数の異なる要求が発生する。ここで個別に型を書くと、将来`User`のフィールドが増えた瞬間にすべて修正漏れが起きる。これこそが技術負債の始まりだ。

—

1. Partial:すべてをオプショナルにする優しさ

`Partial`は、オブジェクトのすべてのプロパティを`?`付き(オプショナル)に変換する魔法だ。主に「更新用のデータ」を作る際によく使う。

// 更新リクエストを送る際の型
// { id?: string; name?: string; email?: string; age?: number; } と同義
type UpdateUserDto = Partial;

const updateData: UpdateUserDto = {
name: “新しい名前”, // これだけでOK
};

現場の知見:
`Partial`は便利だが、ネストされたオブジェクトの内部までは再帰的に変換してくれない。「DeepPartial」が必要なケースも多いが、まずは標準の挙動を理解しよう。

2. Required:甘えを許さない強制力

逆に、`Partial`の対極にあるのが`Required`だ。APIのレスポンスで`age?: number`となっていても、クライアント側で「この画面では絶対に値が入っているはずだ」と断定したいとき、無理やりオプショナルを剥がすのに使う。

// すべてのプロパティが必須になる
type ValidatedUser = Required;

// エラー: ageが欠けているため型チェックで怒られる
const user: ValidatedUser = {
id: “1”,
name: “田中”,
email: “tanaka@example.com”,
};

3. Pick と Omit:型を切り出す外科手術

これが実務で最も出番が多い。

  • Pick: Tの中からKで指定したプロパティだけを「選ぶ」。
  • Omit: Tの中からKで指定したプロパティを「取り除く」。

「特定の画面で必要なデータだけを抽出する」という目的は同じだが、アプローチが逆だ。

// 特定の画面では ID と Name だけが必要
type UserSummary = Pick;

// ID 以外が欲しい場合(Omitは「除外」する)
type UserWithoutId = Omit;

const summary: UserSummary = { id: “1”, name: “佐藤” };

現場の知見:
「どちらを使うべきか?」と迷ったら、「増える可能性」を考えろ。
もし将来的にプロパティがどんどん増えるインターフェースなら、必要なものだけを明示する`Pick`の方が安全だ。逆に、特定の機密情報(passwordなど)を確実に除外したい場合は、`Omit`の方がコードがスッキリする。

—

裏側で何が起きているのか?(TypeScriptのコンパイラ挙動)

君たちが普段何気なく使っている `Partial` は、実は以下のような型定義の糖衣構文に過ぎない。

type MyPartial = {
[P in keyof T]?: T[P];
};

ここで使われているのは、「Mapped Types(マップ型)」という強力な機能だ。
`[P in keyof T]` という構文は、「Tのキーをすべて取り出して、それを一つずつPとして処理する」というループ処理を行っている。`?`を付けることでオプショナルにし、`T[P]`で元の型を復元しているんだ。

TypeScriptは、コンパイル時にこれらのユーティリティ型を「計算」して、最終的なインターフェースの形に変換する。ブラウザが実行するJavaScriptにはこの型情報は一切残らない。だからこそ、どれだけ複雑な型を書いても、ランタイムのパフォーマンスには影響しないんだ。安心して使い倒していい。

—

最後に:シニアからのアドバイス

君がもし「型が複雑になりすぎて、IDEが重い……」と感じ始めたら、それはユーティリティ型の使いすぎというより、抽象化の抽象化をしすぎている証拠かもしれない。

ユーティリティ型は、「コードのメンテナンス性を上げるため」の道具であって、「型パズルを楽しむため」の道具ではない。

1. まずは公式のユーティリティ型を使う。
2. それでも足りなければ、自分でMapped Typesを書いてみる。
3. それでも手に負えなくなったら、素直に型を分割する。

この順序を忘れないようにしてほしい。型は君たちの敵ではなく、最強の味方だ。エディタが赤く波線を引いてくれるのは、君のバグを事前に防いでくれている「親切な警告」なんだから。

さあ、エディタを開いて、さっそく今のプロジェクトの冗長な型定義をリファクタリングしてみようぜ。応援している。

コメント

タイトルとURLをコピーしました