前回、ちらっとMapped Typesのプロパティの型を extends
で切り替える方法を書いたが、他にもいろいろな指定方法があるのでメモ。
環境
TypeScript v4.7.4 で確認。
実装例
以下のtypeを用意。
type ExampleType = { one: string two: number three: boolean }
直接指定
最もシンプル。
type ExampleMappedType = { [P in keyof ExampleType]: string } const example: ExampleMappedType = { one: 'one', two: 'two', three: 'three', }
Mapped Typesの記法は { [P in K]: T }
だが、直接指定の場合 Record<K, T>
に置き換えられる。
Conditional Types
前回書いたが、Conditional Typesが使える。
type ExampleMappedType = { [P in keyof ExampleType]: P extends 'one' ? string : number } const example: ExampleMappedType = { one: 'one', two: 2, three: 3, }
P
を用いた型指定
{ [P in K]: T }
の P
を用いた T
の型指定が可能。
プロパティ参照
ブラケット表記法によるプロパティ参照ができる。
type ExampleMappedType = { [P in keyof ExampleType]: ExampleType[P] } const example: ExampleMappedType = { one: 'one', two: 2, three: false, }
参照する型定義は、 P
に含まれるプロパティが存在すればいいため、 [P in K]
の K
の型である必要はない。
type ReferenceType = Record<keyof ExampleType | 'four', string> type ExampleMappedType = { [P in keyof ExampleType]: ReferenceType[P] } const example: ExampleMappedType = { one: 'one', two: 'two', three: 'three', }
関数の引数や戻り値
関数にすることで、 P
を引数や戻り値の型に使える。
as
によるKey Remappingでプロパティ名の変換と合わせて使うと、プロパティ名に対応した関数を宣言できる。
type ExampleMappedType = { [P in keyof ExampleType as `format${Capitalize<P>}`]: (value: ExampleType[P]) => string } const example: ExampleMappedType = { formatOne: value => `one: ${value}`, formatTwo: value => `two: ${value}`, formatThree: value => `three: ${value}`, }
Key Remapping中にConditional Typesの判定を挟むことも可能。
type ExampleMappedType = { [P in keyof ExampleType as P extends 'three' ? `is${Capitalize<P>}` : `get${Capitalize<P>}`]: () => ExampleType[P] } /* これでも同じ type ExampleMappedType = { [P in keyof ExampleType as `${P extends 'three' ? 'is' : 'get'}${Capitalize<P>}`]: () => ExampleType[P] } */ const example: ExampleMappedType = { getOne: () => 'one', getTwo: () => 2, isThree: () => true, }
参考
以下のページが詳しいが、使われている用語が公式ハンドブックにまとめられていない模様。
型引数 P, K, T
の説明に使われている用語は、Mapped types追加のPRから取ったものか?
homomorphic mapped type
は、TypeScriptのissueやFAQに出てくる。
Stack Overflowにも質問があった。
振り返り
Mapped Typesの型指定時にConditional Typesとブラケット表記を使ったところ、これまたコードレビューで「なにこれ?」と聞かれたので書いてみた。
Conditional Typesは、型を指定できるところでは当然のように書けるので、いつの間にやら使っていたが、Mapped Typesと組み合わせて使う例はWeb検索してもあまり出てこない気がする。