やあ、皆さん!TypeScriptの世界へようこそ。伝説のチーフアーキテクト、私がお話ししましょう。
今日はね、TypeScriptの型定義の中でも、特に「Mapped Types」という、ちょっと魔法みたいな機能の中から、プロパティを「あってもなくてもOK」にする`?`(オプショナル)と、「やっぱり必須!」に戻す`-?`(必須化)のテクニックについて、深掘りしていこうと思います。
「なんか難しそう…」って思いました? 大丈夫、大丈夫。私と一緒に、一つずつ丁寧に紐解いていけば、きっと「なるほど!」って膝を打つ瞬間が訪れますから。現場のリアルな知見も交えながら、優しくお話ししていきますね。
Mapped Typesって、そもそも何者?
本題に入る前に、まずは「Mapped Types」って何?ってところから、フワッと理解しておきましょう。
TypeScriptの世界には、オブジェクトの「形」を定義する「型」がありますよね。例えば、ユーザー情報だったり、商品のデータだったり。
// ユーザー情報を表現する型
type User = {
id: number; // ユーザーIDは数値
name: string; // 名前は文字列
email: string; // メールアドレスも文字列
isActive: boolean; // アクティブ状態は真偽値
};
この`User`型、とってもシンプルで分かりやすいですよね。
Mapped Typesというのは、既存の型(この場合は`User`)を「設計図」にして、新しい型の「設計図」を自動的に作り出す、賢い「型変換マシン」だとイメージしてください。
例えば、「`User`型を元に、すべてのプロパティが読み取り専用になった新しい型を作りたい!」とか、「すべてのプロパティが、あってもなくてもいい(オプショナル)型を作りたい!」なんていう時に、このマシンが大活躍するんです。一つ一つ手作業で新しい型を定義し直すなんて、泥臭い作業は、このTypeScriptの現場ではご法度ですよ!
プロパティを「あってもなくてもOK」にする `?` の魔法
さて、本題に入りましょう。まずは「`?`」の魔法です。これは皆さん、普段の型定義でもお馴染みかもしれませんね。
type Product = {
id: string;
name: string;
price: number;
description?: string; // ここに注目!`?`が付いているので、descriptionはあってもなくてもOK
};
// descriptionプロパティがなくてもエラーにならない
const myProduct: Product = {
id: “P001”,
name: “スペシャルコーヒー豆”,
price: 1200,
// descriptionがなくてもOK!
};
// descriptionプロパティがあってもOK
const anotherProduct: Product = {
id: “P002”,
name: “オリジナルマグカップ”,
price: 800,
description: “手に馴染む、こだわりの一品です。”, // あってもOK!
};
この`?`は、プロパティが「オプショナル(Optional)」、つまり「任意」であることを示します。例えるなら、お店で商品を買うときの「レシートの有無」みたいなものです。欲しい人はもらうし、いらない人はもらわない。どちらでも良い、ってことですね。
Mapped Typesで「全部オプショナル」にする!
この「`?`」の魔法を、Mapped Typesを使って一気に、しかも自動的に付与することができるんです。その代表格が、TypeScriptに最初から用意されている`Partial
`Partial
例えるなら、お店で「商品の注文書」を書くとき、通常は全部の項目を埋めないといけないですよね? でも、`Partial`を使うと、その注文書をコピーして、「どの項目も仮でOK!記入しなくてもOKな『仮注文書』」を自動で作ってくれる、そんなイメージです。
実際のコードを見てみましょう。
// ユーザー情報を表現する型 (先ほどのUser型を再利用)
type User = {
id: number;
name: string;
email: string;
isActive: boolean;
};
// Partial
type PartialUser = Partial
/
PartialUser型は、実質的に以下と同じ意味になります
type PartialUser = {
id?: number;
name?: string;
email?: string;
isActive?: boolean;
};
/
// PartialUser型は、どのプロパティも定義しなくてもエラーにならない
const partialUserData: PartialUser = {
// nameプロパティだけ定義してもOK!
name: “山田 太郎”,
};
// もちろん、全てのプロパティを定義してもOK!
const fullPartialUserData: PartialUser = {
id: 1,
name: “佐藤 花子”,
email: “hana@example.com”,
isActive: true,
};
// idプロパティだけ定義してもOK!
const idOnlyData: PartialUser = {
id: 99,
};
console.log(partialUserData); // { name: ‘山田 太郎’ }
console.log(fullPartialUserData); // { id: 1, name: ‘佐藤 花子’, email: ‘hana@example.com’, isActive: true }
console.log(idOnlyData); // { id: 99 }
どうですか? `Partial
この`Partial
type Partial
[P in keyof T]?: T[P]; // ここがポイント!
};
ちょっと呪文みたいに見えますよね? 大丈夫、分解して解説します。
- `[P in keyof T]`: これは「`T`型が持っているプロパティの名前を一つずつ取り出して、それらを`P`という変数に入れる」という意味です。`keyof T`は`T`型が持つ全てのプロパティ名(キー)の集合を表します。
- `?:`: そして、取り出したそれぞれのプロパティ`P`に、この`?`(オプショナル修飾子)を付けます。
- `T[P]`: そのプロパティ`P`の元の型(`T`型における`P`の型)をそのまま使います。
つまり、「`T`型から全てのプロパティ名を引っ張り出してきて、その一つ一つに`?`をつけて、元の型をそのまま適用した新しい型を作る」というわけです。なるほど、賢い「型変換マシン」ですよね!
プロパティを「やっぱり必須!」に戻す `-?` のチカラ
さて、ここからが今日のメインテーマの一つ、`-?`修飾子のお話です。
一度オプショナルになったプロパティを、「やっぱりこれ、必須にしたい!」と元に戻す、あるいは、もともとオプショナルだったものを強制的に必須にする、そんな時にこの`-?`が活躍します。
例えるなら、「仮注文書」では記入が任意だった項目を、「本注文書にするから、ここだけは絶対書いてね!」と、特定の部分を強制的に必須に戻すようなイメージです。
TypeScriptには、すべてのプロパティを必須に戻す`Required
// 商品情報を表現する型
type Item = {
id: string;
name: string;
price?: number; // priceはオプショナル
category: string;
};
// Item型をPartial
type AllOptionalItem = Partial
/
AllOptionalItem型は以下と同じ
type AllOptionalItem = {
id?: string;
name?: string;
price?: number;
category?: string;
};
/
// ここからが本番!AllOptionalItem型をRequired
type AllRequiredItem = Required
/
AllRequiredItem型は以下と同じ
type AllRequiredItem = {
id: string;
name: string;
price: number; // priceも必須に戻った!
category: string;
};
/
// AllRequiredItem型は、全てのプロパティが必須なので、一つでも欠けるとエラーになる
const completeItem: AllRequiredItem = {
id: “I001”,
name: “高級紅茶”,
price: 3000, // priceプロパティも必須!
category: “飲料”,
};
// これはエラーになります! priceとcategoryが足りません。
/
const incompleteItem: AllRequiredItem = {
id: “I002”,
name: “ティーカップ”,
};
/
console.log(completeItem); // { id: ‘I001’, name: ‘高級紅茶’, price: 3000, category: ‘飲料’ }
`Required
この`Required
type Required
[P in keyof T]-?: T[P]; // ここがポイント!`-?`
};
これも先ほどの`Partial`と同じように分解してみましょう。
- `[P in keyof T]`: これは`Partial`と同じで、「`T`型が持っているプロパティの名前を一つずつ取り出して、それらを`P`という変数に入れる」ですね。
- `-?:`: ここがミソです! この`-`(マイナス記号)は、「オプショナル修飾子`?`を取り除く」という意味なんです。つまり、もしプロパティに`?`が付いていたら、それを削除して必須に戻す、という指示になります。
- `T[P]`: プロパティ`P`の元の型をそのまま使います。
つまり、「`T`型から全てのプロパティ名を引っ張り出してきて、もし`?`が付いていたらそれを取り除き、元の型をそのまま適用した新しい型を作る」というわけです。
この`-?`は、TypeScript 2.8から導入された「修飾子の削除(Removal of Modifiers)」という強力な機能の一部なんです。`readonly`修飾子を外す`-readonly`もありますよ。現場では、一時的に読み取り専用にしたいけど、特定の処理では書き込みを許可したい、なんて時に重宝します。
現場でどう使う? 具体的なシナリオ
さて、こんな魔法みたいな機能、実際の開発現場でどう役立つのか、気になりますよね?
例えば、Webアプリケーションでユーザー情報を更新するAPIを考えてみましょう。
1. ユーザー登録時: 全ての情報(名前、メールアドレスなど)が必須です。
2. ユーザー情報更新時: ユーザーが変更したい項目だけを送ればよく、全ての項目を送る必要はありません。
この「更新時」のデータ型を定義する際に、`Partial
type UserProfile = {
userId: string;
userName: string;
email: string;
birthDate: string; // 例えば ‘YYYY-MM-DD’ 形式
profileImage?: string; // プロフィール画像は任意
isActive: boolean;
};
// ユーザー情報を更新するAPIのペイロード型
// Partial
type UpdateUserProfilePayload = Partial
// ユーザー情報更新の例
function updateUserProfile(userId: string, data: UpdateUserProfilePayload) {
console.log(`ユーザーID: ${userId} の情報を更新します。`, data);
// … 実際にAPIを呼び出す処理 …
}
// 名前の更新だけを行いたい場合
updateUserProfile(“user-123”, {
userName: “新しい名前”,
});
// メールアドレスとプロフィール画像の更新を行いたい場合
updateUserProfile(“user-123”, {
email: “new_email@example.com”,
profileImage: “new_image_url.jpg”,
});
// isActiveをfalseにしたい場合
updateUserProfile(“user-123”, {
isActive: false,
});
どうですか? `Partial
そして、`-?`(`Required
あるいは、特定のプロパティだけを必須に戻す、といったカスタムなMapped Typeを作ることも可能です。
// Partial
type UpdateUserProfileWithRequiredName = Partial
Required
/
この型は、以下のような意味になります
type UpdateUserProfileWithRequiredName = {
userId?: string;
userName: string; // ここが必須に戻った!
email?: string;
birthDate?: string;
profileImage?: string;
isActive?: boolean;
};
/
// userNameは必須なので、これを欠くとエラーになる
const validUpdate: UpdateUserProfileWithRequiredName = {
userName: “田中 太郎”, // userNameは必須
email: “tanaka@example.com”, // 他はオプショナル
};
// エラー: ‘userName’ プロパティが型 ‘{ email: string; }’ にありません。
/
const invalidUpdate: UpdateUserProfileWithRequiredName = {
email: “invalid@example.com”,
};
/
console.log(validUpdate); // { userName: ‘田中 太郎’, email: ‘tanaka@example.com’ }
少し複雑に見えるかもしれませんが、`&`(インターセクション型)や`Pick
まとめ: TypeScriptの型は「設計図の柔軟性」
今日の話をまとめましょう。
- Mapped Typesは、既存の型を元に新しい型を自動生成する「賢い型変換マシン」です。
- `?`修飾子をプロパティに付与すると、そのプロパティは「オプショナル(任意)」になります。
- `Partial
`は、`T`型の全てのプロパティをオプショナルにするユーティリティ型です。 - `-?`修飾子をプロパティに付与すると、もし`?`が付いていた場合はそれが削除され、そのプロパティは「必須」に戻ります。
- `Required
`は、`T`型の全てのプロパティを必須にするユーティリティ型です。
これらの機能は、特にAPIの設計や、コンポーネントのPropsの型定義など、柔軟なデータ構造を扱う場面で真価を発揮します。初めは少し呪文のように感じるかもしれませんが、これらの概念を理解し、実際に手を動かして試してみることで、TypeScriptの奥深さと便利さが実感できるはずです。
大丈夫、皆さんのTypeScriptの旅はまだ始まったばかり。一歩ずつ、着実に進んでいきましょう。この「設計図の柔軟性」をマスターすれば、あなたの書くコードは、もっと強く、もっとしなやかになりますよ!
それでは、また次の記事でお会いしましょう!

コメント