【実務・中級編】 Partialによる全プロパティの任意化 – TypeScript実践ガイド

こんにちは。君もそろそろ、実務のコードベースで「あれ、一部のフィールドだけ更新するAPIのペイロード、どう型定義しよう……」と頭を悩ませるフェーズに来た頃じゃないかな。

Reducerのstate管理や、バックエンドへ投げるPATCHリクエストのDTO(Data Transfer Object)設計。こういう場面で、元の型をわざわざコピペしてすべてに `?` を手打ちしているコードを見かけると、お兄さんは正直、夜しか眠れなくなってしまうんだよ。

今回は、TypeScriptの中級への扉を叩く君たちに向けて、標準ユーティリティ型の一つである `Partial` について、実務で本当に使える知見をたっぷり叩き込んでいこう。公式ドキュメントには書いていない、コンパイラの裏側の話や、実務特有の「落とし穴」まで包み隠さず話すから、コーヒーでも飲みながら聞いてくれ。

—

1. `Partial` とは何か? なぜ実務で必須なのか

一言で言えば、「ある型 `T` が持つすべてのプロパティを、強制的にオプショナル(省略可能)にする」という魔法のユーティリティ型だ。

例えば、ユーザーのプロフィール情報を表す `User` 型があったとする。

type User = {
id: string;
name: string;
email: string;
age: number;
};

このユーザーの「メールアドレスだけ」を更新するAPIを叩くとき、わざわざ `id` や `name` を必須で送る必要はないよね。そんなときに `Partial` を使うと、コンパイラは以下のように解釈してくれる。

// Partialの実態はこれと同じ
type PartialUser = {
id?: string;
name?: string;
email?: string;
age?: number;
};

すべてのプロパティに `?` が付与されるため、どのプロパティも省略可能(`undefined` を許容、あるいは存在しない状態)になる。実務におけるフォームのバリデーション結果の保持や、PATCHリクエストのペイロード作成にはなくてはならない存在なんだ。

—

2. TypeScriptの裏側:マッピング型(Mapped Types)の正体

「へえ、便利ですね。じゃあどうやって動いてるんですか?」と思ったそこの君、非常にいい着眼点だ。
裏側のメカニズムを知らずして、シニアのエンジニアとは語れない。

実は `Partial` は、TypeScriptの標準ライブラリ(`lib.es5.d.ts`)の中で、以下のようにたった数行で定義されている。

type Partial = {
[K in keyof T]?: T[K];
};

これが何を意味しているのか、分解して解剖してみよう。

1. `keyof T`: 型 `T` のプロパティ名をユニオン型としてすべて取り出す(例: `”id” | “name” | “email” | “age”`)。
2. `in`: そのユニオン型をループ(イテレート)する。
3. `[K in keyof T]`: 各プロパティ名 `K` を順に処理していく。
4. `?`: ここが肝心。ループで生成されるすべてのプロパティに対して、オプショナル修飾子を強制付与する。
5. `T[K]`: 元のプロパティが持っていた型(ルックアップ型)をそのまま維持する。

つまり、`Partial` は魔法のキーワードではなく、TypeScriptが持つマッピング型(Mapped Types)という強力な機能を使った、極めてエレガントな「型変換スクリプト」に過ぎないんだ。この仕組みを知っていれば、将来的に「特定のプロパティだけをオプショナルにしたい(派生型の作成)」という応用問題にも直面したとき、自分でカスタムユーティリティ型を書けるようになるはずさ。

—

3. 実務で即コピペできる!実践的なコード例

百聞は一見にしかず。実際のフロントエンド開発で、どういう風に `Partial` を組み込むべきか、きれいなサンプルコードを用意した。
そのままエディタに貼り付けて挙動を確認してみてほしい。

/

  • ユーザー情報のベースとなる型

/
type UserProfile = {
id: string;
username: string;
bio: string;
website: string;
};

/

  • 模擬APIクライアント:ユーザー情報を部分的に更新する関数
  • @param userId 更新対象のユーザーID
  • @param updates 変更したいフィールドのみを含むオブジェクト

/
async function updateUserProfile(userId: string, updates: Partial): Promise {
console.log(`User ${userId} の更新リクエストを送信します`, updates);

// 実務ではここで fetch や axios を使って PATCH リクエストを飛ばす
// await axios.patch(`/api/users/${userId}`, updates);
}

// — 使用例 —

// 1. 全プロパティを渡さなくても、TypeScriptの型チェックを通過する
// (bioとwebsiteを変更しない場合、これらを省略できる)
const handlePartialUpdate = async () => {
await updateUserProfile(“user_123”, {
username: “new_frontend_master”,
// bio や website は未定義(undefined)のまま渡さなくてよい
});
};

// 2. もしタイポしたり、存在しないプロパティを渡そうとすると…
const invalidUpdate = async () => {
await updateUserProfile(“user_123”, {
// ❌ コンパイルエラー:
// Type ‘{ usrnme: string; }’ is not assignable to type ‘Partial‘.
// Object literal may only specify known properties, and ‘usrnme’ does not exist in type ‘Partial‘.
usrnme: “typo_detect_test”,
} as any); // ※実際の現場で ‘as any’ で型エラーを握り潰すのは絶対NGだからね!
};

どうだろう?
「一部のフィールドだけを安全に扱いたい」というユースケースにおいて、`Partial` がどれだけ強力にボツプロパティの混入やタイポを防いでくれるか、肌で感じられたんじゃないかな。

—

4. 現場のシニアが教える「`Partial` の罠とベストプラクティス」

さて、ここからが一番大事な話だ。
実務で `Partial` を使い始めると、大体みんな同じ壁にぶつかる。ジュニアから中級にステップアップする君たちには、最初からこの知見を共有しておこう。

罠1: ネストしたオブジェクト(深層のプロパティ)はオプショナルにならない!

これは本当によくある勘違いだ。以下のコードを見てほしい。

type DeepUser = {
id: string;
profile: {
firstName: string;
lastName: string;
};
};

type ShallowPartial = Partial;

この時、`ShallowPartial` の型はどうなると思う?
正解は、`profile` 自体はオプショナルになるが、`profile` の中にある `firstName` や `lastName` は必須(Required)のままなんだ。

const user: ShallowPartial = {
// profile自体を丸ごと省略することは可能だが…
};

const invalidUser: ShallowPartial = {
profile: {
// ❌ コンパイルエラー! firstName が足りないと言われる
// ‘firstName’ is missing in type ‘{ lastName: string; }’ but required in type ‘{ firstName: string; lastName: string; }’.
lastName: “Smith”
}
};

【解決策】
もしAPIの更新などで、ネストしたオブジェクトの中身すらも部分的に更新したい場合は、標準の `Partial` では力不足だ。その場合は、以下のような再帰的(Recursive)な Partial を自分で定義するか、コミュニティで実績のあるライブラリ(`type-fest` など)の `PartialDeep` を使おう。

// 再帰的にすべての階層をオプショナルにするカスタム型の例
type DeepPartial = {
[K in keyof T]?: T[K] extends object ? DeepPartial : T[K];
};

※実務では、バックエンドの設計段階でオブジェクトを深くネストさせず、フラットなDTOにしてもらう方がフロントエンドの型管理としては圧倒的に楽になるケースが多い。このあたりはバックエンドチームとの交渉力見せ所だね。

罠2: `undefined` と「プロパティが存在しない」の混同

`Partial` を適用したオブジェクトのプロパティを参照するとき、その値は `T[K] | undefined` になる。
そのため、実行時において「キー自体が存在しない(`in` 演算子でfalse)」のか、「キーは存在するが値が `undefined` なのか」を厳密に区別する必要がある場面が出てくる。
JSONにシリアライズしてバックエンドに送る際、`{ name: undefined }` というプロパティが含まれることで、バックエンド側のバリデーションライブラリ(ZodやJoiなど)で予期せぬエラーを踏むことがある。送信直前に `undefined` のキーを削除するユーティリティ関数を一枚挟むのが、プロの現場でのスマートな立ち回りだと言える。

—

5. おわりに:型は「仕様のドキュメント」である

TypeScriptの型定義は、単なるエラーチェックの道具じゃない。
「この画面、このAPIでは、どのデータが必須で、どこが省略可能なのか」という生きた仕様書そのものなんだ。

今回学んだ `Partial` を適切に使いこなせるようになると、コードの安全性が跳ね上がるだけでなく、他の開発者がコードを見たときに「あ、この処理は一部更新なんだな」と一目で意図が伝わる、圧倒的にメンテナンス性の高いコードベースを作ることができる。

明日からのコードレビューで、誰かが手動で `?` をペタペタ貼っていたら、「ここ、`Partial` 使えますよ」とさりげなくアドバイスしてあげてくれ。
君のその一言が、チーム全体の型レベルを確実に引き上げるはずだ。それじゃあ、今日も最高のコードを書こうぜ!

コメント

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