こんにちは!フルスタックエンジニアの先輩です。
TypeScriptの型システムの世界へようこそ。
今回は、実務で本当によく使う「関数の引数をMapped Types(マップ型)で自在に変化させる方法」についてお話ししますね。ここをクリアすると、TypeScriptの型システムが持つ「オブジェクトの形を自由自在に変形する魔法」の片鱗をバッチリマスターできますよ。
設定オブジェクトを受け取る関数を書いていて、こんなもどかしさを感じたことはありませんか?
- 「基本は全部の設定が揃っていてほしい(Required)けれど、一部だけ後から上書きできるようにしたい(Partial)」
- 「新規作成のときは必須だけど、更新のときは任意のプロパティにしたい」
これを愚直に別々のインターフェースで定義しようとすると、コードが重複してメンテナンス地獄になってしまいます。
TypeScriptのMapped Typesを使えば、元となる設計図をベースにして、コンパイラに型を自動計算させることができるんです。
さっそく、その仕組みと実践的な使い方を一緒に見ていきましょう!
—
1. Mapped Types(マップ型)の基本イメージ
Mapped Typesとは、一言で言うと「既存のオブジェクト型のプロパティをループして、新しい型を再構築する仕組み」です。
JavaScriptの `Array.prototype.map()` をイメージしてください。あれの「型版」だと思って間違いありません。
[元の型] (UserConfig)
├── id: string (必須)
├── timeout: number (必須)
└── retries: number (必須)
↓ Mapped Types による変形 (Partial化)
[新しい型] (PartialUserConfig)
├── id?: string (任意)
├── timeout?: number (任意)
└── retries?: number (任意)
TypeScriptには、標準でこのMapped Typesを使った便利なユーティリティ型がいくつか用意されています。今回はその中でも代表的な `Partial
—
2. 実践:設定オブジェクトを柔軟に操る関数を作る
それでは具体的なコードを見ていきましょう。
今回は、アプリケーションのネットワーク通信設定を行う `setupClient` という関数を題材にします。
まずは、すべての設定が揃っている「完全な設計図(Base)」を定義します。
// 1. すべてのプロパティが必須である「ベースの型」を定義します
interface ClientConfig {
endpoint: string;
timeout: number;
retries: number;
debugMode: boolean;
}
パターンA:初期化時はすべて必須、更新時は一部だけ変えたい!
アプリケーションの起動時はすべての設定が必要ですが、後からデバッグモードだけ変えたい、あるいはタイムアウトだけ変えたいというケースがありますよね。
ここで `Partial
// 内部的な実装:Partial
// 渡されたプロパティだけを上書きできるようにします
function updateClientConfig(
currentConfig: ClientConfig,
// 引数の patches は、ClientConfig の一部分だけでもOKになる!
patches: Partial
): ClientConfig {
// スプレッド構文で既存の設定に新しい設定を上書き
return {
…currentConfig,
…patches,
};
}
// — 実際の使用例 —
const baseConfig: ClientConfig = {
endpoint: “https://api.example.com”,
timeout: 5000,
retries: 3,
debugMode: false,
};
// 成功例:timeoutだけを変更するパッチを渡す
const updated = updateClientConfig(baseConfig, {
timeout: 10000, // timeout だけ指定しても怒られない!
});
console.log(updated);
/
出力結果:
{
endpoint: ‘https://api.example.com’,
timeout: 10000, // ここが変わる
retries: 3,
debugMode: false
}
/
`Partial
// TypeScriptがコンパイル時に作り出す型のイメージ
type _InferredPartial = {
endpoint?: string;
timeout?: number;
retries?: number;
debugMode?: boolean;
}
すべてのプロパティに `?`(オプショナル修飾子)が付与されるため、一部のプロパティだけを渡すことが許されるようになるわけです。
—
パターンB:逆に、曖昧な設定を「すべて必須」に強制する (`Required`)
今度は逆のパターンです。
ユーザーから受け取る初期設定オブジェクトが、あちこち省略可能(オプショナル)で作られているとします。しかし、システム内部で処理する前には「すべての値が確実に入っている状態(Required)」に強制したいですよね。
そんなときは `Required
// ユーザーが入力する設定(一部が省略可能かもしれない)
interface UserInputConfig {
endpoint?: string;
timeout?: number;
retries?: number;
debugMode?: boolean;
}
// システム内部の処理関数
function initializeSystem(config: Required
console.log(`System initialized with endpoint: ${config.endpoint}`);
}
// — 実際の使用例 —
// エラーになるケース(一部が欠けている)
/
initializeSystem({
endpoint: “https://api.example.com”,
// ほかのプロパティが抜けているので、コンパイルエラー!
});
/
// 成功例:すべて揃えている場合
initializeSystem({
endpoint: “https://api.example.com”,
timeout: 3000,
retries: 5,
debugMode: true, // すべて揃っているのでコンパイル通ります
});
`Required
—
3. 陥りやすい文法エラーと注意点
ここで、初学者がやりがちな「惜しいミス」を一つご紹介します。
Mapped Typesを使うとき、自分でカスタムした変形型を作ろうとして次のような書き方をしてしまうことがあります。
// 【やってしまいがちなエラー例】
type BadPartial
// エラー:プロパティの修飾子を正しく扱えていない、またはインデックスシグネチャの書き方ミス
[K in keyof T]?: T[K];
};
単純なオブジェクト型であればこれでも動きますが、複雑なジェネリクスや、既にオプショナルになっているプロパティ、あるいは `readonly`(読み取り専用)属性がついているプロパティを扱う際、意図通りに型が評価されないことがあります。
TypeScriptには最初から `Partial
車輪の再発明をする前に、まずはこれら標準のMapped Typesを組み合わせて関数定義をスッキリさせるのが、モダンで堅牢なTypeScriptコードを書く近道です。
—
まとめ
- Mapped Types(マップ型) とは、既存のオブジェクト型のプロパティを走査して新しい型を作り出す仕組み。
- `Partial
` を使えば、関数の引数オブジェクトのプロパティをすべて「任意(省略可能)」に変換できる(部分的な上書きに便利!)。 - `Required
` を使えば、逆にすべてを「必須」に強制変換できる(設定の抜け漏れチェックに便利!)。
この型変形のテクニックが手に入ると、APIクライアントやコンポーネントのProps設計など、あらゆる場面で「柔軟性と堅牢性を両立したコード」が書けるようになります。
ここをクリアしたあなたなら、もうTypeScriptの基本の型システムはバッチリマスターできていますよ!自信を持って次のステップへ進んでくださいね。