【TypeScript】const アサーションと型エイリアスを組み合わせた型定義 （enum の代替手段）

# はじめに
TypeScript には、**const アサーション**と**型エイリアス**を組み合わせて型を定義する方法があります。

この記事では、具体的な例を通じて、この手法を実践的にどのように活用するかを解説します。

# const アサーションとは？
**const アサーション**は、オブジェクトや配列のリテラルで使用します。

以下のように、`ActionType` は `const` で定義した定数であるため、再代入することはできません。ただし、定数内のプロパティは変更できます。
```typescript
const ActionType = {
  Get: 0,
  Post: 1,
  Put: 2,
  Delete: 3,
};

const ActionType = null;
// 定数であるため、'ActionType' に代入することはできません。

ActionType.Post = 10; // プロパティは変更は可能
```
そこで**const アサーション**の出番です。`}` のあとに `as const` を記述することで、後からプロパティが変更されることを防ぎます。
```typescript
const ActionType = {
  Get: 0,
  Post: 1,
  Put: 2,
  Delete: 3,
} as const;

// 読み取り専用プロパティであるため、'Post' に代入することはできません。
ActionType.Post = 10; // 
```
以上見ていきたように、**const アサーション**（`as const`）を使用することで、オブジェクトや配列のプロパティが後から変更されることを防ぐことができます。

また、const アサーションを使用することで、オブジェクトや配列の各プロパティの型が**具体的に推論**されます。この利点に関しては const アサーションと型エイリアスを組み合わせた型定義の説明の中で解説します。

# 型エイリアスとは
**型エイリアス**は、既存の型に別名を付けることができる機能です。`type` キーワードを使用して、型エイリアスを宣言します。型エイリアスは、コードの可読性を向上させ、複雑な型定義を簡略化するために使用されます。

```typescript
// 型エイリアスの宣言
type StringOrNull = string | null;

let subtitle: StringOrNull = "Foo Bar";
let subtitle_2: StringOrNull = null;
```
`[]` や `keyof`、`keyof` と組み合わせることで、より複雑で柔軟な型エイリアスを作成することができます。
```typescript
// typeof キーワードを使用した型エイリアスの宣言
let user = { id: 10, name: "John Doe", is_verified: true };
// typeof キーワードは、変数やオブジェクトの型を取得します。
// type User = { id: number; name: string; is_verified: boolean} と同義
type User = typeof user; 

// インデックスアクセス型: [] を使用すると、オブジェクトのプロパティの型を参照できる
// type UserId = number と同義
type UserId = User["id"];
// type UserIdAndName = string | number と同義
type UserIdAndName = User["id" | "name"];

// keyof と組み合わることも可能
// type UserProp = "id" | "name" | "is_verified" と同義
type UserProp = keyof typeof user;

// type UserValues = string | number | boolean と同義
type UserValues = User[UserProp];
// type UserValues = (typeof user)[keyof typeof user];
```

# const アサーションと型エイリアスで型定義
const アサーションと型エイリアスを組み合わることで、ある値だけを許容する型を定義することができます。

```typescript
const ActionType = {
  Get: 0,
  Post: 1,
  Put: 2,
  Delete: 3,
} as const;

// typeof ActionType は { Get: 0, Post: 1, Put: 2, Delete: 3 }

// プロパティ名の型
type ActionTypeKeys = keyof typeof ActionType;
// keyof typeof ActionType は "Get" | "Post" | "Put" | "Delete"

// プロパティ値の型
type ActionTypeValues = (typeof ActionType)[keyof typeof ActionType];
// ActionTypeValues は 0 | 1 | 2 | 3 

// '10' を型 'ActionTypeValues' に割り当てることはできません
let action_type: ActionTypeValues = 10;
// ただし、`as const` を省略すると、エラーメッセージはなくなる
```
const アサーションを使用することで、オブジェクトや配列の各プロパティの型が具体的に推論されます。例えば、`ActionTypeValues` は ` 0 | 1 | 2 | 3 ` を推論されます。もし、`as const` をつけなければ、`number` と推論されます。



以下は `ActionTypeValues` を使用した具体的な関数の例です。
```typescript
type Action = {type: ActionTypeValues, id: number, content: string};

let action: Action = {type: ActionType.Delete, id: 123, content: ""};
performAction(action)

function performAction(action: Action) {
  switch (action.type) {
    case ActionType.Get:
      console.log("Performing GET request");
      break;
    case ActionType.Post:
      console.log("Performing POST request");
      break;
    case ActionType.Put:
      console.log("Performing PUT request");
      break;
    case ActionType.Delete:
      console.log("Performing DELETE request");
      break;
    default:
      console.log("Unknown HTTP method");
  }
}
```

# おわりに
const アサーションと型エイリアスを組み合わせた型定義を紹介しました。紹介した型定義と同じ役割を果たすものとして、TypeScript には `enum`（列挙型） がありますが、`enum` は問題点がしばしば指摘されます。本記事で紹介した方法は `enum` の代替手段になります。