はじめに
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にすることが推奨されています。
- TypeScriptをプロダクト開発に使う上でのベストプラクティスと心得:1-3-3. Re-exports 問題
- Ambient const enums are not allowed when the '--isolatedModules' flag is provided.
では、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ライフを