[Typescript] 「なぜ enum の利用が推奨されないのか？」をまとめてみた

## はじめに

Typescript にも他の言語と同様にenumが記法として実装されているのですが、次の記事ではenumではなく、Union typeを利用しようという内容が記載してありました。

[さようなら、TypeScript enum: Kabuku Developers Blog](https://www.kabuku.co.jp/developers/good-bye-typescript-enum)

色々関連する記事を読んだ結論として、特にこだわりがなければ「Union type」を利用する方が良いと僕も思います。
その根拠 / 背景を以降まとめたいと思います。

なお、本記事のコードの検証には、[Typescript Playground](https://www.typescriptlang.org/play) を利用しています。

## Typescriptのenumについて
enumはある型についてとりえる値を列挙します。(他の言語と同様)
Typescriptでは文字列、または数値を値としてマッピングできます。
デフォルトでは、上から0,1,2...というように数値を自動で値として設定します。

```typescript
enum Language {
    Japanese, // 0 
    English, // 1
    Chinese // 2
}

//明示的に文字列を割り当てることもできる
enum Size {
    Small = 'small',
    Medium = 'medium',
    Large = 'large'
}
```

enumを利用する際には通常は `Language.Japanese`のようにアクセスをすると思いますが、
enumの仕様として数値を値として利用している場合には、下記のように値からの逆引きができます。

```typescript
enum Language {
    Japanese, // 0 
    English, // 1
    Chinese // 2
}

console.log(Language.English) // => 1
console.log(Language[0]) // => "Japanese"
```

この値からの逆引きについては```const enum ```を利用することで制限することも可能です。


## enumの利用を避けた方がいいとされる主な理由
enum (const enum)の利用を避けた方がいい根拠は主に下記の3つかと思います。
( 他にもちょこちょこ細かい点はありますが。)

### 1. 値に数値をマッピングした場合に意図しない数値でアクセスする記述ができてしまう
数値を値としてマッピングしたenumは値からキーの逆引きができて、場合によっては便利な側面もあるのですが、下記のような意図しないアクセスをコンパイル時に検出することができません。

```typescript
enum Language {
    Japanese, // 0 
    English, // 1
    Chinese // 2
}
//OK
console.log(Language[0]) // => "Japanese"
//NG
console.log(Language[5]) // => undefined

```

```const enum```を利用した場合でも値からの逆引きを避けることはできますが、下記のような関数の引数として、enumを利用する場合に不用意なアクセスを防ぐことができません。

```typescript

const enum Language {
    Japanese, English, Chinese
}

function getAbbreviation(lang: Language) {
  switch(lang) {
    case Language.Japanese: return 'jp'
    case Language.English: return 'en'
    case Language.Chinese: return 'zh'
    default: return ''
  }
}
//コンパイルエラーは発生しない
getAbbreviation(9)
```

### 2. 利用していないenumの値があってもTree-shakingがうまく働かずバンドルされてしまう
この点については、[TypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介します](https://engineering.linecorp.com/ja/blog/typescript-enum-tree-shaking/)
で詳細にまとめてくださっています。

簡単に紹介すると、enumはJSにコンパイルされる際には、即時実行関数を利用するため、バンドラー側で未使用かどうかを判断できません。
その結果、enumの各値がコード上で使用されている / いないに関わらずコンパイル後のjsファイルに含まれてしまいます。

```js
// enum Language のコンパイル結果
var Language;
(function (Language) {
    Language[Language["Japanese"] = 0] = "Japanese";
    Language[Language["English"] = 1] = "English";
    Language[Language["Chinese"] = 2] = "Chinese";
})(Language || (Language = {}));
```

### 3. アンビエントコンテキストにおけるconst enumの使用はコンパイルエラーになることがある
[さようなら、TypeScript enum: Kabuku Developers Blog](https://www.kabuku.co.jp/developers/good-bye-typescript-enum)で詳細に説明してくださっているのですが、 `--isolatedModules コンパイルオプション`(ファイルごとに単一のモジュールとしてコンパイルを行えるようにするオプション)を利用するとコンパイルエラーになります。

ちなみに、このオプションはBabelやwebpackでトランスパイルを行う場合やアンビエントコンテキストにおいてはtrueにすることが推奨されています。

* [TypeScriptをプロダクト開発に使う上でのベストプラクティスと心得:1-3-3. Re-exports 問題](https://qiita.com/jagaapple/items/ce0da04be28c35dc7d4f#1-3-3-re-exports-%E5%95%8F%E9%A1%8C)
* [Ambient const enums are not allowed when the '--isolatedModules' flag is provided.](https://github.com/microsoft/TypeScript/issues/20703#issuecomment-359092263)

## では、enum みたいな記述をしたい場合にどう表現するか？

結論としては、すでにいろいろな箇所で指摘されているようにtypeを利用して実装するが良いと思います。

上記で利用していた```enum Language```は下記のように置き換えられます。(enumのように値からのキー逆引きはできません。)

```typescript

type Language = "Japanese" | "English" | "Chinese"
```

このようにtypeを利用することで関数の引数などで意図せぬ値が入力された場合でもコンパイル時に気づくことができます。

enumの各値にも意味がある場合にはObjectに```const assertion, keyof, typeof```を利用することで、Union typeを表現できます。

```typescript

const ACTIVITY_LEVEL = {
  LITTLE : 1.2,
  LIGHT: 1.375,
  MODERATE: 1.55, 
  HEAVY: 1.725,
  VERY_HEAVY: 1.9
} as const

type ActivityLevel = typeof ACTIVITY_LEVEL[keyof typeof ACTIVITY_LEVEL]
```
↑の keyof, typeof の詳しい解説については下記の記事にまとめましたので、よければご参照ください。
参考: [[Typescript] typeof "object value" [keyof typeof "object value"] の動作を丁寧に解説してみる](https://qiita.com/saba_can00/items/bdefb28a1873658cf5d9)


## 最後に
enumの利用が推奨されない原因を調べることで、型をどうやったらうまく利用できるかについて色々と学ぶことができました。
それでは、良いTypescriptライフを

## 参考リンク

* [さようなら、TypeScript enum: Kabuku Developers Blog](https://www.kabuku.co.jp/developers/good-bye-typescript-enum)
* [TypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介します](https://engineering.linecorp.com/ja/blog/typescript-enum-tree-shaking/)
* [TypeScriptをプロダクト開発に使う上でのベストプラクティスと心得:1-3-3. Re-exports 問題](https://qiita.com/jagaapple/items/ce0da04be28c35dc7d4f#1-3-3-re-exports-%E5%95%8F%E9%A1%8C)
* [Ambient const enums are not allowed when the '--isolatedModules' flag is provided.](https://github.com/microsoft/TypeScript/issues/20703#issuecomment-359092263)