こんにちは、ソフトウェアエンジニアのhamanokamiです!
最近のマイブームは某極麻婆豆腐で、辛いものを食べると腹痛が発生するリスクがあることを知りつつもついつい食べてしまいます。
さて今回はTypeScriptのMapped Typesについて書いていきます。
書こうと思った背景は、Software DesignのTypeScript特集でMapped Typesの記事内容がなかなか理解ができなかったのでMapped Typesの理解を促進させたいためです。
Mapped Typesとは?
Mapped Typesは「ある型から類似した別の型を生成」したいときに使用します。
TypeScriptの公式Docでは変更不可能な型から同じ構造で変更可能な型を生成する例が紹介されています。
// Removes 'readonly' attributes from a type's properties type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>;
この例だとCreateMutable
がMapped Typesにあたります。
具体的に何をしているかというと、
- Genericsで受け取った型のプロパティ名をkeyof型演算子でユニオン型で返却(
Property in keyof Type
の部分) - 1で返却されたプロパティにreadonlyがある場合、readonlyを除く(
-readonly
の部分)
です。
よって、UnlockedAccount
型は、
type UnlockedAccount = { id: string; name: string; };
になります。
もしMapped Typeを使わない場合、
type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = { id: string; name: string; };
のように2つ型を定義する必要があり、もしAccount
という情報にnickname
を追加したいとなった場合、LockedAccount
とUnlockedAccount
の両方にnickname
を追加する必要がありますが、Mapped Typeを使用した例ではLockedAccount
のみで対応が済みます。
少し実用的なMapped Typesの使用例
今回は私の好きなアナログレコードの型からレコード情報を画面表示用文字列に変換して値を返却するメソッド型を生成をしてみます。
まずアナログレコードの型を用意します。
type Vinyl = { artist: string; title: string; inch: number; releaseYear: number; };
このアナログレコード型の情報を画面表示用文字列に変換する関数式型の定義は下記になります。
type VinylFormatter = { [Property in keyof Vinyl as `format${Capitalize<Property>}`]: (value: Vinyl) => string; };
`format${Capitalize<Property>}`
はTemplate Literal Typesを使用し、プロパティ名を例えばartist
からformatArtist
に書き換えています。
あとはVinylFormatter
型を使った関数式を定義すればオッケー。
const vinylFormatter: VinylFormatter = { formatArtist: (artist) => artist, formatTitle: (title) => title, formatInch: (inch) => `${inch}インチ`, formatReleaseYear: (releaseYear) => `${releaseYear}年`, };
このようにすればVinyl
型にプロパティを追加したとき、画面表示用関数の対応漏れに気が付きやすくなります。※追加する必要性があるかは別ですが、、、
実際にどう動くかはPlaygroundを用意したので確認してみてください。 www.typescriptlang.org
VinylFormatter
について他の情報でも使用できそうな型なので、Genericsを使い汎用的に使用できるように拡張してみるとこのようなコードになります。
type Vinyl = { artist: string; title: string; inch: number; releaseYear: number; }; type Cd = { artist: string; title: string; discs: number; releaseYear: number; }; type ItemFormatter<Type> = { [Property in keyof Type as `format${Capitalize<string & Property>}`]: (value: Type) => string; }; const vinylFormatter: ItemFormatter<Vinyl> = { formatArtist: (value) => value.artist, formatTitle: (value) => value.title, formatInch: (value) => `${value.inch}インチ`, formatReleaseYear: (value) => `${value.releaseYear}年`, }; const cdFormatter: ItemFormatter<Cd> = { formatArtist: (value) => value.artist, formatTitle: (value) => value.title, formatDiscs: (value) => `${value.discs}枚`, formatReleaseYear: (value) => `${value.releaseYear}年`, };
こちらもPlaygroundを用意しているのでどうぞ。
さいごに
今回Mapped Typesと合わせて他の型再利用の方法についても理解が深まる良い機会になりました。引き続き他のTypeScriptの表現を学んでいこうと思います!
\\『真のユーザーファーストでマーケットを創造する』仲間を募集中です!! //