こんにちは!フロントエンドの現場を渡り歩いているチーフアーキテクトの私です。
TypeScriptの学習、毎日お疲れ様です!
「基本の型はやっと一通り触れるようになったぞ」という段階に差し掛かると、次にやってくるのが「型を自在に変形させる魔術」、そう、Mapped Types(マップ型)の世界ですよね。
公式ドキュメントを覗いてみると、なんだか難解な記号が並んでいて、思わずそっとタブを閉じたくなる気持ち、痛いほどよく分かります。私も昔はそうでした。
でも、安心してください。今日はその中でも、ちょっと強面だけど実はめちゃくちゃ頼りになる「as句を使ったキーの再マッピング」という機能について、身近な例えを交えながら、とことん優しく解き明かしていきたいと思います。
コーヒーでも飲みながら、リラックスして読んでいってくださいね。
—
そもそも「Mapped Types」ってなんだろう?
新しい機能に入る前に、まずはベースとなるMapped Typesのおさらいをしておきましょう。
イメージしてみてください。あなたは文房具屋さんで、ノートにラベルを貼る作業をしています。
「赤のノート」「青のノート」「黄色のノート」……と、すでにあるノートの名前をコピーして、新しいラベルを次々と作っていく作業、これがMapped Typesの基本です。
TypeScriptの世界で言うと、「既存のオブジェクトの型をベースにして、一括で新しい型を作り直す仕組み」のことですね。
// おなじみの基本の形
type Fruits = {
apple: string;
banana: string;
};
// これをベースに、値の型をすべて「boolean」に変えたい!という時にMapped Typesを使います
type CheckList = {
[K in keyof Fruits]: boolean;
};
// 出来上がる型:
// { apple: boolean; banana: boolean; }
「ほうほう、既存のキーをそのまま使って、中身の型を変えるんだな」というのが分かればバッチリです。
—
主役登場:「as句」でキーの名前を変えてみよう
さて、ここからが本題です。
先ほどは「キーの名前はそのまま」でしたが、現実のWeb開発ではこんなワガママな要望がよく出てきます。
> 「データベースから取ってきたデータのキー、全部小文字なんだけど、画面表示用に頭文字に `on` を付けたいんだよね……」
> 「あるいは、特定の不要なキーだけはこの型からこっそり除外したい!」
そんな時に使うのが、Mapped Typesの中でこっそり使える魔法の言葉、`as` 句(キーの再マッピング)です。
身近な例え:お買い物の「ラベル貼り替え作業」
想像してください。
あなたは雑貨屋さんで、海外から届いた商品のダンボール箱の仕分けをしています。
箱には「hat(帽子)」「shoes(靴)」という英語のラベルが貼ってあります。でも、日本の店頭に並べるためには、すべてのラベルを「item_hat」「item_shoes」という親切な名前に貼り替えなければいけません。さらに、「いらないチラシの束」みたいな不要な箱は、この作業の途中でゴミ箱にポイッと捨てる(フィルタリングする)必要があります。
この「名前の書き換え」と「不要なものの排除」を、TypeScriptの型の上でやってくれるのが `as` 句なんです。
—
実際にコードを書いてみよう
百聞は一見にしかず。先ほどの「お買い物のラベル貼り替え」を、TypeScriptのコードで再現してみましょう。
1. キーの名前を自動で改造する(プレフィックスの付与)
まずは、オブジェクトのキーの頭文字に `on` をペタッとくっつける例を見てみます。
テンプレートリテラル型(バッククォートで文字列を囲むやつですね)と組み合わせると、これがめちゃくちゃエレガントに書けます。
// 元となるユーザーの設定データ(キーは小文字)
type UserSettings = {
theme: string;
notifications: boolean;
sound: number;
};
// 【as句の登場!】
// キー(K)の名前を `on${Capitalize
type ActionHandlers = {
[K in keyof UserSettings as `on${Capitalize
};
/
出来上がる型(ActionHandlers)の姿:
{
onTheme: (value: string) => void;
onNotifications: (value: boolean) => void;
onSound: (value: number) => void;
}
/
どうでしょう? `[K in keyof UserSettings as …]` の部分で、「キーの名前をこう変えてね!」とTypeScriptにお願いしています。
`Capitalize` という便利な型ユーティリティも使って、頭文字を綺麗に大文字に変換していますね。これぞプロの技って感じでちょっとカッコいいですよね。
2. 特定のキーをこっそり消去する(フィルタリング)
次は、「不要なものは除外する」パターンです。
実は `as` 句の後ろで `never` という特殊な型を返してあげると、TypeScriptは「あ、このキーは作らなくていいんだな」と察して、型の中から綺麗に消し去ってくれます。
「えっ、`never` ってなんだっけ?」と思った方、大丈夫です。ここでは「存在しないものにする魔法の消しゴム」だと思っておいてください。
type Product = {
id: number;
name: string;
secretCode: number; // これだけは外部に出したくない!
};
// “secretCode” 以外を残すMapped Types
type PublicProduct = {
[K in keyof Product as K extends ‘secretCode’ ? never : K]: Product[K];
};
/
出来上がる型(PublicProduct)の姿:
{
id: number;
name: string;
// secretCode は綺麗に消えている!
}
/
`K extends ‘secretCode’ ? never : K` という条件分岐(三項演算子に似た書き方ですね)を使って、「もしキーが `secretCode` なら `never`(消す)、それ以外ならそのままのキー (`K`) を使う」という判定をしています。
これ、実際のAPIのレスポンス型を整形する時などにめちゃくちゃよく使うテクニックなので、ぜひ覚えて帰ってくださいね。
—
実務でつまずきやすいポイントと温かいアドバイス
ここまで読んで、「なるほど、便利そう!」と思っていただけたなら嬉しいです。
ただ、実際に自分でエディタを開いて書き始めると、きっとこんな壁にぶつかります。
> 「あれっ、`K` を文字列として扱いたいのに、エラーが出る……?」
そうなんです。Mapped Typesのループ変数 `K` は、初期状態では `string | number | symbol` という広い心(広すぎる型)を持っています。そのため、そのまま文字列操作のメソッドやテンプレートリテラルに放り込むと、TypeScriptが「本当に文字列なの?」と心配して怒り出すことがあります。
そんな時は、今回のコード例でもこっそり使っていた `K & string` というテクニックを思い出してください。
「Kは文字列ですよ」と優しく肩を叩いて教えてあげることで、TypeScriptも安心して型変換を進めてくれるようになります。
最初は複雑な記号の羅列に見えるかもしれませんが、一つひとつ分解して眺めてみれば、やっていることは「お買い物のラベル貼り替えと整理整頓」と同じです。
焦らず、自分のペースでエディタにコードをコピペして、マウスカーソルを当てて型の変化を確かめながら遊んでみてくださいね。TypeScriptは、あなたのペースにちゃんと寄り添ってくれる優しい相棒ですから。
それでは、また次回の記事でお会いしましょう!快適な型ライフを!

コメント