【Typescript】結局 as const って何してるの？

# コード例

foods2の型が以下のようになる理由について理解するのが目標です！

```tsx
// foods1の型は string[]
const foods1 = ["rice", "curry", "apple"];

// foods2の型はreadonlyの["rice", "curry", "apple"]型
const foods2 = ["rice", "curry", "apple"] as const;
```

# 4つの効果

`as const`を使うと、以下の4つの効果を得ることができます。

1. 配列リテラルの型推論結果を配列型ではなくタプル型にする
2. オブジェクトリテラルから推論されるオブジェクト型はすべてのプロパティがreadonlyになる。配列リテラルから推論されるタプル型もreadonlyタプル型になる
3. 文字列・数値・BigInt・真偽値リテラルに対してつけられるリテラル型がwideningしないリテラル型になる
4. テンプレート文字列リテラルの型がstringではなくテンプレートリテラル型になる

出典：プロを目指す人のためのTypeScript入門

# コード例解説

```tsx
// 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`で宣言された変数は別の値が再代入される可能性があるためです。
    
    ```tsx
    // nameは"tanaka"型ではなくstring型に推論される（wideningされている）
    let name1 = "tanaka";
    
    // nameは"tanaka"型に推論される
    const name2 = "tanaka";
    ```

### 1. 配列リテラルの型推論結果を配列型ではなくタプル型にする

まず、型推論結果を配列型ではなくタプル型で受け取ることになります。

タプル型は簡単にいうと、要素の型を指定した配列の型です。型が指定されていることで、`string`型の値が持つプロパティやメソッドを使うことができます。ちなみに型を指定していない要素にアクセスすることはできません。

```tsx
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 追記：本章の説明、コード例を大幅に修正しました)

こちらは今回のコード例では出てきませんが、具体例は以下の通りです。
テンプレート文字列リテラルは、バッククウォート（`）で区切られたリテラルです。バッククウォートを使うことで、文字列内に変数を埋め込むことができたり、改行ができたりととても便利です。

```tsx
let name = "tanaka";
const message = `Hello, ${name}!`;

// Hello, tanaka! と出力される
console.log(message);
```

テンプレート文字列リテラルで宣言された値は、通常の型推論では`string`型に推論されます。しかし、`as const`をつけることで、型推論をより厳密なものにすることができます。以下の例では、変数`name`の型は`string`型なので、`Message`型は`"Hello, ${string}"`型になっています。`${string}`の部分は、　`string`型の値ならなんでもOKということを指します。

```tsx
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`ではなくテンプレートリテラル型になります。


# いつ使える？

### 値から型を作る

```tsx
const foods = ["rice", "curry", "apple"] as const;

// type Foods = "rice" | "curry" | "apple"
type Foods = (typeof foods)[number];
```

Lookup型とtypeofを使うことで、上記のように値から型を作ることができます。

ただ、型は値やオブジェクトの姿を表す最上位のものであり、これをもとに実装を行なっていくという考え方からすると、値から型を作るのは不適切かもしれません。

### 変更を許さないことを示す

`as const`で宣言されたものは値の変更も追加もできません。そのため、`as const`を使うことで「これは定数であり変更することは許されない。なので安心して使ってね」ということを開発者に示すことができます。