コード例
foods2の型が以下のようになる理由について理解するのが目標です!
// foods1の型は string[]
const foods1 = ["rice", "curry", "apple"];
// foods2の型はreadonlyの["rice", "curry", "apple"]型
const foods2 = ["rice", "curry", "apple"] as const;
4つの効果
as const
を使うと、以下の4つの効果を得ることができます。
- 配列リテラルの型推論結果を配列型ではなくタプル型にする
- オブジェクトリテラルから推論されるオブジェクト型はすべてのプロパティがreadonlyになる。配列リテラルから推論されるタプル型もreadonlyタプル型になる
- 文字列・数値・BigInt・真偽値リテラルに対してつけられるリテラル型がwideningしないリテラル型になる
- テンプレート文字列リテラルの型がstringではなくテンプレートリテラル型になる
出典:プロを目指す人のためのTypeScript入門
コード例解説
// foods1の型は string[]
const foods1 = ["rice", "curry", "apple"];
// foods2の型はreadonlyの["rice", "curry", "apple"]型
const foods2 = ["rice", "curry", "apple"] as const;
foods1は単純な型推論の結果です。配列の各要素はwideningされ、string
型と推論されています。string型を要素に持つ配列なので、foods1はstring[]
型に推論されているというわけです。
foods2にはas const
がついています。これにより、4つの効果を受けることになります。
-
wideningとは
型をより抽象度の高いものに拡張すること。
let
で変数が宣言されたとき、リテラル型ではなくプリミティブ型に推論されます。これは、let
で宣言された変数は別の値が再代入される可能性があるためです。// nameは"tanaka"型ではなくstring型に推論される(wideningされている) let name1 = "tanaka"; // nameは"tanaka"型に推論される const name2 = "tanaka";
1. 配列リテラルの型推論結果を配列型ではなくタプル型にする
まず、型推論結果を配列型ではなくタプル型で受け取ることになります。
タプル型は簡単にいうと、要素の型を指定した配列の型です。型が指定されていることで、string
型の値が持つプロパティやメソッドを使うことができます。ちなみに型を指定していない要素にアクセスすることはできません。
const list: [number, string, boolean] = [1, "test", true];
list[0].toExponential(); // OK
list[1].length; // OK
list[2].valueOf(); // OK
list[3] = "new value"; // NG
as const
をつけることで、foods2は要素が3つのタプル型([string, string, string]
)に推論されます。
2. オブジェクトリテラルから推論されるオブジェクト型はすべてのプロパティがreadonlyになる。配列リテラルから推論されるタプル型もreadonlyタプル型になる
この効果により、foods2はreadonlyになります。つまりfoods2はreadonlyのタプル型(readonly [string, string, string]
)ということになります。ちなみに、要素がオブジェクトの時、オブジェクトの中身もreadonlyとして推論されます。再帰的にreadonlyになるということです。
3. 文字列・数値・BigInt・真偽値リテラルに対してつけられるリテラル型がwideningしないリテラル型になる
今の所foods2の型はreadonly [string, string, string]
ですが、この効果の影響でタプル型の各要素の型がwideningしないリテラル型になります。
つまり、foods2の型はreadonly ["rice", "curry", "apple"]
になります。
説明の都合上、一つ目の効果の説明の時に各要素がwideningされているかのように表現しましたが、実際のところはas constをつけたものについてはwideningが行われないという認識の方が間違いがないでしょう。
4. テンプレート文字列リテラルの型がstringではなくテンプレートリテラル型になる
(2024/05/23 追記:本章の説明、コード例は誤りです。近日中に修正します)
(2024/05/25 追記:本章の説明、コード例を大幅に修正しました)
こちらは今回のコード例では出てきませんが、具体例は以下の通りです。
テンプレート文字列リテラルは、バッククウォート(`)で区切られたリテラルです。バッククウォートを使うことで、文字列内に変数を埋め込むことができたり、改行ができたりととても便利です。
let name = "tanaka";
const message = `Hello, ${name}!`;
// Hello, tanaka! と出力される
console.log(message);
テンプレート文字列リテラルで宣言された値は、通常の型推論ではstring
型に推論されます。しかし、as const
をつけることで、型推論をより厳密なものにすることができます。以下の例では、変数name
の型はstring
型なので、Message
型は"Hello, ${string}"
型になっています。${string}
の部分は、 string
型の値ならなんでもOKということを指します。
let name = "tanaka";
const message = `Hello, ${name}!` as const;
// Message型は "Hello, ${string}"型になる
type Message = typeof message;
const testMessage1: Message = "Hello, suzuki!"; // OK!
const testMessage2: Message = "Hello, sato!"; // OK!
このように、as const
をつけることでテンプレート文字列リテラルの型がstring
ではなくテンプレートリテラル型になります。
いつ使える?
値から型を作る
const foods = ["rice", "curry", "apple"] as const;
// type Foods = "rice" | "curry" | "apple"
type Foods = (typeof foods)[number];
Lookup型とtypeofを使うことで、上記のように値から型を作ることができます。
ただ、型は値やオブジェクトの姿を表す最上位のものであり、これをもとに実装を行なっていくという考え方からすると、値から型を作るのは不適切かもしれません。
変更を許さないことを示す
as const
で宣言されたものは値の変更も追加もできません。そのため、as const
を使うことで「これは定数であり変更することは許されない。なので安心して使ってね」ということを開発者に示すことができます。