【入門編】Mapped TypesとInterfaceの組み合わせで実現する「部分更新」の型安全化 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!フロントエンドからNode.jsまで、TypeScriptのことなら何でも聞いてくださいね。

今回は、TypeScriptの学習において誰もが一度はぶ壁であり、同時に「型システムの美しさ」に感動する瞬間でもある「Mapped Types(マップ型)」と「インターフェース(Interface)」を組み合わせた、実践的な「部分更新(PATCHリクエスト)」の型安全化について解説します。

「APIの更新処理で、送りたいプロパティだけを安全に指定したいのに、全部の項目が必須になって怒られてしまう…」そんな悩みを抱えたことはありませんか?

ここをクリアすれば、あなたもTypeScriptの型システムを自分の手足のように操れるようになりますよ。さあ、一緒に本質をマスターしていきましょう!

—

1. そもそも「部分更新(PATCH)」ってなに?

Webアプリケーションを作っていると、ユーザー情報を更新するAPI(PATCHリクエストなど)を実装することがよくありますよね。

例えば、次のような「ユーザーのプロフィール情報」を表すインターフェースがあるとします。

interface UserProfile {
id: string;
name: string;
email: string;
age: number;
}

データベースを全上書きする(PUTリクエスト)なら、この `UserProfile` 型をそのまま使えばバッチリです。しかし、「名前(name)だけを変えたい」「年齢(age)だけを変えたい」という部分更新(PATCH)の場合はどうでしょう?

通常の `UserProfile` だと、`id`, `email`, `age` も送らないといけない(あるいは送らないとコンパイルエラーになる)ため、非常に不便ですよね。かといって、すべてのプロパティに `?`(オプショナル)をつけてしまうと、今度は「絶対に送らなきゃいけない `id` まで消えてしまう」という別のバグを生む原因になります。

この問題をスマートに解決するのが、「InterfaceとMapped Typesの融合」です。

—

2. 救世主「Mapped Types(マップ型)」の仕組み

TypeScriptには、既存の型をベースにして、新しい型を動的に作り出す魔法のような仕組みがあります。それが Mapped Types です。

まずはイメージ図を見てみましょう。

[ 元のInterface (UserProfile) ]
id: string
name: string
email: string
age: number
│
▼ (Mapped Typesでループして「?」を付与!)
[ 新しいPartial型 ]
id?: string
name?: string
email?: string
age?: string

TypeScriptには、これをあらかじめ用意してくれている `Partial` というユーティリティ型がありますが、今回はその内部で何が起きているのかを理解するために、自分で同じものを作ってみましょう。

自作 `Partial` 型のコード

// T という「型(Interfaceなど)」を受け取り、その中のすべてのプロパティをオプショナル(?)にする型
type MyPartial = {
// [K in keyof T] は、「Tが持つすべてのプロパティ名(K)を一つずつ取り出してループする」という意味
[K in keyof T]?: T[K];
};

たったこれだけです!このコードの呪文のような構文を、一つずつ分解して紐解いてみましょう。

1. `keyof T`

  • `UserProfile` 型を渡した場合、`”id” | “name” | “email” | “age”` という「プロパティ名のユニオン型」を取り出します。

2. `in` 演算子

  • JavaScriptの `for…in` のように、ユニオン型の中からプロパティ名を1つずつ取り出して走査(マッピング)します。

3. `?`(オプショナル修飾子)

  • 取り出したプロパティをすべて「あってもなくてもいい(省略可能)」に変換します。

4. `T[K]`

  • 元の型 `T` における、プロパティ `K` の型(例: `name` なら `string`)をそのまま維持します。

—

3. 実践:部分更新の型安全化を実装してみよう

では、先ほど作った `MyPartial`(あるいは標準の `Partial`)を使って、実際のAPIリクエスト関数を書いてみましょう。

ここでは、`id` だけは更新時に「必須(どのユーザーを更新するか特定するため)」とし、それ以外のプロフィール情報は「部分更新可能」にする、より実用的なパターンを組み立ててみます。

interface UserProfile {
id: string;
name: string;
email: string;
age: number;
}

// 1. idは必須のまま、それ以外をオプショナルにする高度な型を作る
// Omitでidを除外したものに Partial をかけ、最後に id を必須として結合する
type UpdateUserPayload = {
id: string; // idは絶対に必要!
} & Partial>;

// 2. PATCHリクエストを送る関数を定義
function updateyuUser(payload: UpdateUserPayload) {
// 実際の通信処理(ここではコンソール出力)
console.log(“APIに送信するデータ:”, payload);
}

// — 【OKな例】 —
// idと、変えたいプロパティ(nameだけ)を指定する
updateyuUser({
id: “user_123”,
name: “新しい名前(太郎)”,
}); // 💡 コンパイルエラーなし!

// — 【NGな例①:idがない】 —
updateyuUser({
name: “次郎”,
});
// ❌ コンパイルエラー!
// 「Property ‘id’ is missing in type ‘{ name: string; }’ but required in type ‘UpdateUserPayload’.」
// (idがないよ!とTypeScriptが怒ってくれます)

// — 【NGな例②:存在しないプロパティを入れた】 —
updateyuUser({
id: “user_123”,
address: “東京都”, // UserProfileに存在しないプロパティ
});
// ❌ コンパイルエラー!
// 「Object literal may only specify known properties, and ‘address’ does not exist in type …」
// (そんなプロパティはUserProfileにないよ!と防いでくれます)

このコードの素晴らしさが伝わるでしょうか?
開発者がうっかり `id` を渡し忘れたり、タイポして存在しないプロパティを書き込もうとしたりしても、実行する前に(エディタ上で赤波線を出して)TypeScriptが完璧に防いでくれるのです。

—

4. 陥りやすい文法エラーと注意点

Mapped Typesを自分で書き始めると、初心者のうちは次のようなエラーに遭遇しがちです。

① 修飾子の向き(`+?` と `-?`)に戸惑う

Mapped Typesでは、プロパティにオプショナルを「付与」するだけでなく、「剥奪」することもできます。

// すでにオプショナルになっているプロパティから、強制的に「?」を奪ってすべて必須にする型(Requiredの自作)
type MyRequired = {
[K in keyof T]-?: T[K]; // -? と書くと「オプショナルを外す」意味になります
};

「`+?`(つける)」または単に `?`、「`-?`(外す)」という修飾子の存在を知っておくと、型の表現力が一気に広がりますよ。

—

まとめ:型を「組み立てる」楽しさを知ろう

今回は、InterfaceとMapped Typesを組み合わせて、APIの部分更新を型安全にする方法を解説しました。

  • Interface は、オブジェクトの「設計図」である。
  • Mapped Types (`[K in keyof T]`) は、その設計図をループして、新しい形に「変形」させる魔法の型である。
  • これらを組み合わせることで、`id` は必須、他は任意といった現場で本当によくある複雑な要件を完全に型安全にできる。

ここをクリアできれば、TypeScriptの基本はもうバッチリマスターしています!型は単なるエラーチェックの道具ではなく、あなたのコードの意図を正確に伝える「最高のドキュメント」です。

ぜひ、実際のプロジェクトのPATCHリクエストなどで試してみてくださいね。それでは、次のステップへ進みましょう!

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