209
129

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-01-13

はじめに

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

さようなら、TypeScript enum: Kabuku Developers Blog

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

なお、本記事のコードの検証には、Typescript Playground を利用しています。

Typescriptのenumについて

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

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

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

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

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は値からキーの逆引きができて、場合によっては便利な側面もあるのですが、下記のような意図しないアクセスをコンパイル時に検出することができません。

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

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


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の観点で紹介します
で詳細にまとめてくださっています。

簡単に紹介すると、enumはJSにコンパイルされる際には、即時実行関数を利用するため、バンドラー側で未使用かどうかを判断できません。
その結果、enumの各値がコード上で使用されている / いないに関わらずコンパイル後の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で詳細に説明してくださっているのですが、 --isolatedModules コンパイルオプション(ファイルごとに単一のモジュールとしてコンパイルを行えるようにするオプション)を利用するとコンパイルエラーになります。

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

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

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

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


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

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

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


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"] の動作を丁寧に解説してみる

最後に

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

参考リンク

209
129
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
209
129

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?