TypeScriptを触りはじめると、「型があるってなんて安心なんだ!」と感動する一方で、少し複雑な型を作ろうとした瞬間に、謎の記号の壁にぶつかって頭を抱えてしまうことってありますよね。
「あれ、この型を全部オプショナルにしたいだけなのに、どう書くんだっけ…?」
「`readonly`を剥がしたいのに、エラーが消えない…!」
大丈夫ですよ、安心してください。最初はみんなそこで立ち止まります。魔法の呪文のように見えるTypeScriptの高度な型操作も、一つひとつの部品をほどいていけば、ちゃんと仕組みが見えてきます。
今回は、そんなTypeScriptの「Mapped Types(マップ型)」という機能の中で、オブジェクトのプロパティの「読み取り専用(readonly)」や「省略可能(?)」を自在に操るための、ちょっとワクワクするテクニックを一緒に見ていきましょう!
—
そもそも「Mapped Types」ってなに?
難しく考える必要はありません。Mapped Typesとは、「すでにある型をベースにして、一括で新しい型を作り出すための型パターンのこと」です。
身近な例えで言うと、お気に入りの「洋服の型紙」を思い浮かべてみてください。
その型紙をベースにして、「布の色だけを変える」「全部のサイズをワンサイズ大きくする」といった一括変更ができたら便利ですよね。Mapped Typesは、まさに型のための「一括変更テンプレート」なんです。
—
主役はこの子たち:「+」と「-」という修飾子
今回のテーマの核心である `readonly` や `?`(オプショナル)ですが、これらを付けるだけでなく、「取り除く」という操作ができるのが、TypeScriptの奥深いところです。
そのときに使うのが、プラス(`+`)とマイナス(`-`)の記号です。
- `+` をつける(または何もつけない): 修飾子を「付与する」
- `-` をつける: 修飾子を「取り除く」
百聞は一見に如かず。具体的なコードでお買い物の場面をイメージしながら見ていきましょう。
1. すべてのプロパティを「読み取り専用」にする(`readonly` の付与)
例えば、ECサイトでユーザーが確定した「注文内容」のデータがあるとします。これは後から勝手に書き換えられては困るので、すべて `readonly`(読み取り専用)にしたいですよね。
// ユーザーが入力したばかりの、まだ普通の注文データ
type Order = {
id: string;
item: string;
price: number;
};
// Mapped Typesを使って、すべてのプロパティに readonly を一括付与する型
type ReadonlyOrder
+readonly [K in keyof T]: T[K];
// ↑ ここにある `+readonly` が、「新しく読み取り専用にしてね」という指示です!
};
// 適用してみよう
type LockedOrder = ReadonlyOrder
const myOrder: LockedOrder = {
id: “12345”,
item: “TypeScriptマスター本”,
price: 3000,
};
// 【エラーになる!】readonlyなので後から書き換えができません
// myOrder.price = 3500;
この `+readonly`(実は `readonly` とだけ書いても同じ意味になります)を使うことで、プロパティが何個あっても一網打尽に「書き換え禁止」にできます。
—
2. 「読み取り専用」の呪縛を解き放つ!(`readonly` の削除)
逆に、「APIから返ってきたデータが全部 `readonly` になっていて、画面の入力フォーム用に一時的に書き換えたいのにエラーになる…!」という絶望的なシチュエーション、実務ではよくあります。
そんなときは、マイナス(`-`)の出番です。
// APIからやってきた、ガチガチに readonly がついたデータ型
type StrictApiData = {
readonly id: string;
readonly title: string;
};
// readonly を取り除く Mapped Types
type Mutable
-readonly [K in keyof T]: T[K];
// ↑ `-readonly` と書くことで、「読み取り専用の呪いを解いてくれ!」と指示しています
};
type EditableData = Mutable
const myData: EditableData = {
id: “A-001”,
title: “はじめようTypeScript”,
};
// 自由自在に書き換えられるようになりました!やったね!
myData.title = “極めようTypeScript”;
この `-readonly` を知っているだけで、「あ、外部ライブラリのガチガチな型を自分でイジれる!」という大きな武器になります。
—
3. 「省略可能(?)」を付けたり消したりする
お次は、オブジェクトのプロパティの後ろにつく `?`(オプショナル)の操作です。フォームの入力をイメージしてください。
- プロパティがなくてもいい(省略可能にする): `?` または `+?`
- 絶対にプロパティがないとダメ(必須にする): `-?`
// ユーザーのプロフィール設定(最初は全部未記入=オプショナルかも?)
type UserProfile = {
name?: string;
bio?: string;
};
// 【ケースA】すべての項目を「絶対に書いてもらう必須項目」に変える型
type RequiredProfile
[K in keyof T]-?: T[K];
// ↑ `-?` をつけることで、オプショナル(?)を剥ぎ取り、必須(Required)にします!
};
type CompleteProfile = RequiredProfile
// エラーになる例:bioが抜けているので怒られます
/
const user: CompleteProfile = {
name: “Taro”,
// bio: “フロントエンドエンジニアです” がないとエラー!
};
/
逆に、完全に入力必須だった型を「下書き保存用に、全部なくてもいいようにしたい!」というときは、`+?`(または単に `?`)を使えば、一瞬でオプショナルな型に変身させることができます。
—
実務でどう使う?TypeScriptに最初から用意されている便利屋さん
ここまでMapped Typesでの `+` と `-` の操作を見てきましたが、実はよく使うお決まりのパターンは、TypeScriptが最初から標準(Utility Types)として用意してくれています。
- `Readonly
` … すべてを `readonly` にする(内部で `+readonly` を使っています) - `Partial
` … すべてをオプショナルにする(内部で `+?` を使っています) - `Required
` … すべてを必須にする(内部で `-?` を使っています)
「じゃあ、わざわざ自分で書く必要ないじゃん!」と思われるかもしれませんが、この仕組み(Mapped Typesと `+` `-` の文法)を知っているかいないかで、型の応用力が何倍も変わります。
例えば、「特定のプロパティだけ `readonly` を外して、他はそのまにしたい」といった、標準のUtility Typesでは手が届かない、ちょっと痒いところに手が届くカスタム型を作れるようになるのは、この仕組みを理解している人だけです。
—
最後に
最初は `[K in keyof T]` や `-?` なんて記号を見ると、「うっ、暗号みたい…」と拒絶反応が出てしまうかもしれません。でも大丈夫。コードはただの「おしゃべり」です。
「この型の、このルールを、ちょっとマイナスして外してあげたいんだよね」
そんなプログラミングの優しい意図を、TypeScriptに伝えるための道具が `+` と `-` です。
焦らず、手元のエディタで少しずつ書き換えながら、その挙動を確かめてみてくださいね。あなたのTypeScriptライフが、ほんの少しでも楽しく、快適になりますように!

コメント