constキーワードに関する新機能
次に紹介するのはconstキーワードに関する新機能です。constは、変数宣言の時に利用しますが、ここでの使い方は、型指定において使われるconstです。具体的に見ていきましょう。
constアサーション
例えば、["RED", "GREEN", "BLUE"]というデータは、RGB色の文字列を、配列として表現したものです。これを変数に代入したリスト4のコードでは、その変数rgbのデータ型は、型推論からstring[]となります。そのため、rgbにYELLOなどの文字列を簡単に追加できてしまいます。
const rgb = ["RED", "GREEN", "BLUE"];
これを防ぐために、例えば、リスト5のコードで定義される読み取り専用のReadonlyRGBなどを用意し、あらかじめ型指定する方法もあります。こうすることで、確かに、rgbには文字列を追加できなくなります。
type ReadonlyRGB = readonly string[]; const rgb: ReadonlyRGB = ["RED", "GREEN", "BLUE"];
一方で、もっと簡単な方法がリスト6のコードです。as constを付け加えるだけで、簡単に読み取り専用のデータが出来上がります。もちろん、これは、配列やタプルだけでなく、プリミティブな変数やオブジェクトにも適用できます。この仕組みをconstアサーションといい、バージョン3.4で導入されました。
const rgb = ["RED", "GREEN", "BLUE"] as const;
constアサーションの場合の型推論
このconstアサーションによるメリットは、単に読み取り専用になるだけではなく、型推論によるその変数のデータ型が狭まることです。例えば、ReadonlyRGBで型定義したrgbの場合は、読み取り専用とはいえ、あくまでそのデータ型はstring[]です。一方、as constで読み取り専用にしたrgbのデータ型は、図1のように、代入した値そのものとなります。
そのため、例えば、GREENという文字列を表すrgb[1]のデータ型も、stringではなく、"GREEN"型となります。これは、プリミティブ型でも同じであり、例えば、リスト7のコードで定義した変数msgのデータ型は、string型ではなく"hello"型となります。
const msg = "hello" as const;
ジェネリクスへのconst指定
このconstによる型を狭める仕組みは、さらに拡張され、バージョン5.0でジェネリクスにも利用できるようになっています。これは、例えば、リスト8のようなコードです。
type RgbO = { rgb: readonly string[]; } function extractRGB<const Colors extends RgbO>(colors: Colors): Colors["rgb"] { return colors.rgb; } const extractedRgb = extractRGB({ // (1) rgb: ["RED", "GREEN", "BLUE"], // (2) colorAdd: "YELLO" // (2) });
関数extractRGB()は、引数オブジェクトに含まれるrgbプロパティの配列を抽出する関数です。そのため、引数の型であるColorsは、必ず読み取り専用String配列であるrgbプロパティが含まれている必要があり、それをジェネリクスのextends RgbOで指定しています。ただし、そのジェネリクスのColorsにはconstキーワードが付与されています。
このように定義した関数に対して、リスト8の(2)のような引数を渡すと、その引数はas constが付与されたのと同じような働きをします。結果、(1)の変数extractedRgbの型はString[]ではなく、図2のように、引数のrgbプロパティのデータと同一、すなわち、["RED", "GREEN", "BLUE"]型となります。
これが、ジェネリクスにconstのないColorsの場合は、図3のようにString[]と型推論されてしまいます。
なお、この仕組みが働くのは、関数の引数に直接リテラルを記述した場合です。リスト8の(2)の引数を、例えば変数colorsとして定義し、次のように、その変数を引数として渡した場合は、extractedRgbのデータ型はString[]と型推論されるので注意してください。
const extractedRgb = extractRGB(colors);
型定義におけるテンプレート文字列
次に紹介するのは、リテラル型にテンプレート文字列が利用できるようになったことです。具体的にみていきましょう。
テンプレート文字列によるリテラル型
リテラル型というのは、前節でも登場しており、リスト7のmsgの"hello"型は、リテラル型になります。ただ、通常は、リスト9の(1)や(2)の型定義のように、ユニオン型と組合せます。
type BlankSpace = "margin" | "padding"; // (1) type Position = "top" | "bottom" | "start" | "end"; // (2)
こうすると、例えば、BlankSpace型の変数には、文字列のmarginかpadding以外は代入できなくなります。例えば、以下のコードはエラーとなるため、綴りミスなどを防げます。
const bs: BlankSpace = "margine";
ここで、リスト9の(1)と(2)を組合せ、例えば、margin-topのようなリテラル型としてBlankSpaceAndPositionを作りたいとします。これまでならば、リスト9の(1)や(2)と同様に、組合せた文字列を列挙したユニオン型の型定義を記述する必要がありました。これが、バージョン4.1でテンプレート文字列を型定義に利用できるようになり、リスト10のコードで組合せの型定義ができるようになりました。これを、テンプレート文字列型といいます。
type BlankSpaceAndPosition = `${BlankSpace}-${Position}`;
テンプレート文字列型と相性がいい新しい型操作
テンプレート文字列型の導入と同時に、つまり、同じくバージョン4.1で、新しい型操作が導入されました。それが、表1の4個です。
型操作 | 内容 |
---|---|
Uppercase | 大文字へ変換 |
Lowercase | 小文字へ変換 |
Capitalize | 最初の文字を大文字へ変換 |
Uncapitalize | 最初の文字を小文字変換 |
例えば、リスト10のBlankSpaceAndPositionを、リスト11のように定義すると、MarginTopのようなキャメル記法のリテラル型が出来上がります。
type BlankSpaceAndPosition = `${Capitalize<BlankSpace>}${Capitalize<Position>}`;