こんにちは!TypeScriptの型定義の旅へようこそ。チーフアーキテクトの私です。
さて、日々フロントエンドの現場でコードを書いていると、「あ、さっき作った型とほとんど同じなんだけど、全部のプロパティを『読み取り専用(readonly)』にしたいな」とか、「全部『オプショナル(?)』にして、後から部分的にデータを流し込みたいな」という場面に、ものすごく頻繁に直面します。
そんなとき、あなたならどうしますか? まさか、同じようなプロパティをコピペして、手動でひとつひとつ書き直していませんか?
「いやいや、それじゃあ保守性もクソもないだろ……」と思ったそこのあなた、大正解です。その泥臭い作業、TypeScriptの「Mapped Types(マップ型)」を使えば、一瞬でエレガントに解決できちゃうんです。
今日は、このマップ型について、難しい専門用語はなるべく抜きにして、お買い物の流れのような身近な例えを交えながら、優しく紐解いていきましょう。大丈夫、一歩ずつ見ていけば絶対に怖くありませんよ!
—
1. マップ型ってなぁに? 身近な例えでイメージしてみよう
いきなりコードを書く前に、頭の中に「型のお弁当箱」をイメージしてください。
例えば、あなたがカフェのメニュー管理システムを作っているとします。そこには「ドリンク」という基本の型(お弁当箱)がひとつありました。
// カフェのドリンクの基本情報
type Drink = {
name: string;
price: number;
stock: number;
};
「名前」「値段」「在庫数」が入っていますね。
さて、季節のキャンペーンが始まりました。「今回のキャンペーンでは、すべての項目を『とりあえず入力必須じゃなくて、あとから埋めてもいい(オプショナル)』状態にしたい!」、あるいは「管理画面用だから、誰も勝手に書き換えられない『読み取り専用(readonly)』にしたい!」という要望が来ました。
ここで登場するのが Mapped Types(マップ型) です。
マップ型は、いわば「型専用のコピー機(あるいは型をリフォームする職人さん)」のようなものです。
元の `Drink` 型をコピー機にセットして、
- 「プロパティを全部オプショナルにして!」
- 「プロパティを全部読み取り専用にして!」
と注文をつけるだけで、わざわざゼロから書き直さなくても、新しい型を自動でガーッと量産してくれる魔法の構文なのです。
—
2. マップ型の基本のキ:ループ処理の仕組みを知ろう
TypeScriptのマップ型は、JavaScriptでいう `map` メソッドや、`for…in` のような「反復処理(ループ)」の感覚にすごく近いです。
まずは、実際のコードで基本のカタチを見てみましょう。
// 1. 元となる普通の型(お買い物リストをイメージしてください)
type ShoppingItem = {
id: number;
name: string;
quantity: number;
};
// 2. マップ型を使って、すべてのプロパティをオプショナル(?付き)にする魔改造型
type PartialShoppingItem = {
// [K in keyof 元の型] という呪文で、プロパティを1つずつ取り出してループさせます
[K in keyof ShoppingItem]?: ShoppingItem[K];
};
// これで作られた PartialShoppingItem は、実質的にこういう型になります:
// {
// id?: number;
// name?: string;
// quantity?: number;
// }
なんだか見慣れない記号が出てきましたね。ここで、つまずきやすいポイントをそっと分解しておきましょう。
1. `keyof ShoppingItem`
- これは、「`ShoppingItem` 型が持っているプロパティの名前たち(`”id” | “name” | “quantity”`)」を、ユニオン型というひとまとまりのリストとして取り出す合言葉です。
2. `[K in …]`
- 「リストの中から、プロパティ名を1個ずつ `K` という変数に入れながら、ぜんぶ網羅してループ(反復)するよ!」という指示です。JavaScriptの `for…of` にちょっと似ていますね。
3. `?:` と `ShoppingItem[K]`
- ループしながら、「型には新しく `?`(オプショナル)を付与してね、中身の型は元の `ShoppingItem` のやつ(`[K]`)をそのまま使ってね」と指定しています。
こうして、元の型を一切汚すことなく、「すべてがオプショナルになった新しいお買い物リストの型」が誕生しました。
—
3. 実務でよく使う!マップ型を便利にする「修飾子」と「再マッピング」
マップ型の基本がわかったところで、現場で「おっ、こいつデキるな」と思われる、ちょっと実用的なテクニックを2つご紹介します。
① 修飾子(Modifiers)で味付けを変える
マップ型では、プロパティの先頭に `+` や `-` をつけることで、属性の「追加」や「削除」をコントロールできます。
- `readonly` をつける:読み取り専用にする
- `-readonly` をつける:読み取り専用を解除する
- `?` をつける:オプショナルにする
- `-?` をつける:オプショナルを必須(Required)にする
例えば、「元からオプショナルになっちゃってる危ういデータを、絶対に全部入力させる(必須にする)型」を作りたいときは、こう書きます。
// フォームの入力途中なので、最初は項目が欠けているかもしれない型
type DraftForm = {
title?: string;
content?: string;
};
// マップ型を使って、マイナス記号でオプショナルを剝ぎ取り、強制的に必須(Required)にする!
type CompleteForm = {
[K in keyof DraftForm]-?: DraftForm[K];
};
// 出来上がった CompleteForm は、title も content も必ず存在しなければならない型になります。
「あ、`?` の前にマイナスをつければ、必須に戻せるんだ!」と知ったときは、私も実務で思わず「なるほどね〜!」と声に出してしまいました。こういう痒いところに手が届くのがTypeScriptの優しいところです。
—
4. 【おまけ】実はTypeScript標準に最初から用意されている便利屋さんたち
ここまでマップ型の仕組みを熱く語ってきましたが、実はTypeScriptの標準ライブラリには、先輩たちがすでに作ってくれた「超よく使われるマップ型」があらかじめ備わっています。
現場では、わざわざ自分で書かずに、これらをインポート(というかグローバルに利用)することがほとんどです。
- `Partial
` : すべてのプロパティをオプショナルにする(今回最初に作ったやつ!) - `Required
` : すべてのプロパティを必須にする - `Readonly
` : すべてのプロパティを読み取り専用にする
// 自分でマップ型をわざわざ定義しなくても、標準の Partial を使えばこう書けます!
type QuickPartialUser = Partial
「なんだ、最初からあるなら自分で書く必要ないじゃん!」と思いましたか?
いいえ、そんなことはありません。標準のユーティリティ型を知るためにも、「あ、裏側ではこうやってマップ型がループして動いているんだな」という仕組み(メカニズム)を理解しておくことが、いざという時の応用力にものすごく繋がるんです。
—
まとめ:マップ型は怖くない!
お疲れ様でした!ここまで読んでくださりありがとうございます。
- Mapped Types(マップ型) は、既存の型のプロパティをコピー機のようにループして、新しい型を自動生成する仕組み。
- `[K in keyof 元の型]` というお決まのフレーズを使えば、簡単に反復処理ができる。
- `+` や `-` を使って、`readonly` や `?` を付けたり外したり自由自在にリフォームできる。
最初は「なんだこの記号の羅列は……」とウッとなってしまうTypeScriptの型定義ですが、ひとつひとつの部品を分解して身近なものに置き換えてみると、実はとても合理的でプログラマーに優しい仕組みだと気づけるはずです。
もし「あれ、ここどういう意味だっけ?」とつまずいてしまったら、いつでもこの記事に戻ってきてくださいね。あなたのTypeScriptライフが、少しでも快適で楽しいものになりますように!

コメント