TypeScriptには、enum(列挙型)という、定数をひとまとめにするための機能が用意されています。
列挙型は、他のプログラミング言語でもよく採用されているデータ構造の1つです。
一見すると便利な機能である enumですが、実際の開発においては、いくつかの理由から使用は避けた方がよいとされています。
そこで今回は、
- enumとはそもそも何か?
- enumの基本的な使い方
といった内容から、非推奨とされている原因や代替手段について詳しく解説していきたいと思います。
TypeScriptにおけるenum(列挙型)とは?
enumとは、列挙型と呼ばれるデータ構造を指すものです。
列挙型とは、関連する定数を一つのグループとしてまとめるための機能のことを言います。
それぞれの定数を任意のキーによって管理することにより、コードの可読性を向上させることができるメリットを持ちます。
Typescriptでは、任意の文字列、または数値を、定数の値として割り当てることができます。
デフォルト (キーのみでenumを定義) の場合は、0,1,2…といった順に、整数値の連番が自動で値として設定されます。
基本的には、定数の中身自体は何でもよい代わりに、値が一意である必要がある場合に使用される機能です。
また、定義したenumは、型として使用することも可能となっています。
enumの使い方
まずは、enumの基本的な使い方から見ていきましょう。
基本構文
enumを定義する際の基本構文は、以下のように記述します。
【基本構文】
enum 列挙型名 {
定数名1,
定数名2,
...
}
定義の際に値を指定しなかった場合、上述の通り、0から順に値が自動で設定されます。
【サンプルコード】
enum Country {
Japan,
America,
China
}
console.log(Country.Japan);
console.log(Country.America);
console.log(Country.China);
【実行結果】
0
1
2
以下の例のように、一部の定数にのみ任意の数値を指定した場合、その他の定数は1つ前の定数の値から続く連番となります。
【サンプルコード】
enum Country {
Japan = 1, // 1
America, // 2
China, // 3
England = 5, // 5
France // 6
}
console.log(Country.Japan);
console.log(Country.America);
console.log(Country.France);
【実行結果】
1
2
6
数値の代わりに文字列を指定することも可能です。
【サンプルコード】
enum Country {
Japan = "日本",
America = "アメリカ",
China = "中国"
}
console.log(Country.Japan);
console.log(Country.America);
console.log(Country.China);
【実行結果】
日本
アメリカ
中国
また、数値型の場合に限りますが、キーから値へのアクセスだけでなく、値からキーへのアクセスも可能とする仕様 (双方向マッピング) となっています。
【サンプルコード】
enum Country {
Japan,
America,
China
}
console.log(Country[0]);
console.log(Country[1]);
console.log(Country[2]);
【実行結果】
Japan
America
China
enumの問題点
enumの基本的な使い方について説明し終えたところで、続いて、TypeScriptにおけるenumが非推奨とされる理由についても解説していきたいと思います。
enumはJavaScriptの仕様から離れすぎている
メジャーな言語のほとんどには標準で搭載されているenumですが、残念ながらJavaScriptには用意されておらず、TypeScriptが独自に実装している機能となります。
そのため、TypeScriptのenumをJavaScriptにコンパイルすると、言語仕様に存在しないものを実現するために即時実行関数を含むコードへと変換され、その関数を経由してオブジェクトが生成されるようになります。
【コンパイル前のコード】
// 数値の場合
enum Direction {
North,
South,
East,
West
}
// 文字列の場合
enum Suit {
Spade = "スペード",
Heart = "ハート",
Diamond = "ダイアモンド",
Club = "クラブ",
}
【コンパイル後のコード】
var Direction;
(function (Direction) {
Direction[Direction["North"] = 0] = "North";
Direction[Direction["South"] = 1] = "South";
Direction[Direction["East"] = 2] = "East";
Direction[Direction["West"] = 3] = "West";
})(Direction || (Direction = {}));
// 文字列の場合
var Suit;
(function (Suit) {
Suit["Spade"] = "スペード";
Suit["Heart"] = "ハート";
Suit["Diamond"] = "ダイアモンド";
Suit["Club"] = "クラブ";
})(Suit || (Suit = {}));
これにより、コードサイズの増加やパフォーマンスの低下に繋がる可能性があるだけでなく、意図しない挙動が起こる原因にもなり得ます。
また、こうしたコードはTree-shakingができないため、使用の有無に関わらずバンドルに含まれてしまう問題もあります。
Tree-shakingとは、バンドル時に使われていないコードを削除する処理のことを言います。
Rollupなどのバンドラーでは、即時実行関数を 「使用されていないコード」 と判断することができないため、未使用の場合でもTree-shakingが適用されず、コードが残ったままとなってしまいます。
型安全性(Type Safety)の点で問題がある
一部の言語では例外もありますが、基本的に列挙型は、宣言時に指定した定数の値以外は変数に代入できないようになっているため、型の安全性が保証されているのもメリットの1つとなっています。
ところがTypeScriptでは、宣言した値以外の数値も代入することができてしまうため、型安全性の観点において問題がある仕様となっています。
以下の例を見てみましょう。
【サンプルコード】
enum Country {
Japan,
America,
China
}
const country: Country = 10; // ここでエラーにならない
console.log(country)
【実行結果】
10
宣言時に自動で割り振られている値は0〜2となりますが、それ以外の数値である10を代入してもエラーが発生せずに正常動作として実行されてしまっています。
ただし、この問題は TypeScript5.0未満で発生するもので、それ以降のバージョンでは仕様が改善されています。
【TypeScript 5.0以降の場合】
enum Country {
Japan,
America,
China
}
const country: Country = 10;
console.log(country);
【実行結果】
error TS2322: Type '10' is not assignable to type 'Country'.
また、値からキーにアクセスする際に、上記の例と同様に存在しない値を指定した場合も、コンパイルエラーにはならずに 「undefined」 となります。
【サンプルコード】
enum Country {
Japan,
America,
China
}
console.log(Country[10]);
【実行結果】
undefined
文字列列挙型だけ公称型になる
ある型に別の型を代入できるかどうかといった 「型の互換性」 の点において、各言語ごとに仕様は異なります。
こうした型システムは、主に 「公称型」 と 「構造的部分型」 の2つに分けられています。
公称型は、JavaやPHPなどで採用されている型システムです。
型の互換性があるか否かを、オブジェクト同士の継承関係に基づいて判断します。
例えば、それぞれの所持するプロパティが全く同一のクラスAとクラスBがあったとします。
構造上は同じであると言えるこの2つのクラスですが、公称型の場合は互いが継承関係になければ互換性があるとは見なされません。
一方、上記の例を互換性があるものと見なすのが構造的部分型です。
構造的部分型は、型の互換性を継承関係ではなく構造が一致しているかどうかで判断します。
TypeScriptの型システムは、一部の例外を除いてこの構造的部分型が採用されています。
そして、その例外の1つに当たるのが文字列列挙型なのです。
以下の例を見てみましょう。
【サンプルコード】
enum NumGroup {
One,
Two
}
enum StrGroup {
Hoge = "hoge",
Fuga = "fuga"
}
// どちらもOK
const num1: NumGroup = NumGroup.One;
const num2: NumGroup = 0;
// 文字列の代入がエラーになる
const str1: StrGroup = StrGroup.Hoge;
const str2: StrGroup = "hoge";
【実行結果】
error TS2322: Type '"hoge"' is not assignable to type 'StrGroup'.
それぞれの列挙型の変数に、同じ列挙型の定数を代入できるのはどちらも同じですが、代入する値を数値型で指定する場合と文字列で指定する場合では、動作が異なるのが分かりますね。
列挙型の中でも、定数の値がどちらの型であるかで処理が変わるため、注意する必要があります。
enumの代替手段
上述の理由から、基本的にenumの使用は非推奨とされていますが、列挙型の機能そのものはコードを記述する上で便利なものです。
そこで、代替手段として使用できるいくつかの記述方法を紹介したいと思います。
ただし、いずれも全く同じ機能を再現できるわけではないので、状況に応じて使い分けるようにするといいでしょう。
Union型を使用する
1つ目の代替案は、Union型を使用する方法です。
Union型とは、2つ以上の型から構成される型であり、特定の値がそれらの型のいずれかに当てはまる可能性を表現するための表記法です。
変数などの型の指定で、通常は1つの型のみを指定するところを、この表記法を使用することで複数の型を指定することができます。
Union型を使用する際は、以下のようにコードを記述します。
【サンプルコード】
let 変数名: string | undefined;
指定したいそれぞれの型を、「 | (パイプ記号)」 で区切って記述します。
また、以下のように記述することで、新たに型名を指定することもできます。
【サンプルコード】
type 型名 = string | undefined;
Union型の応用方法として、特定の値を型に指定し、その値のみを代入可能とする方法があります。
この方法を使用することで、enumと同じように代入する値を制限することができます。
以下の例を見てみましょう。
【サンプルコード】
type Fruits = "Apple" | "Peach" | "Orange";
const myFruits1: Fruits = "Apple"; // 代入可能
const myFruits2: Fruits = "Banana"; // 代入不可
【実行結果】
error TS2322: Type '"Banana"' is not assignable to type 'Fruits'.
実行結果を見ると、「Fruits」 型で指定していない値を変数に代入しようとしたため、エラーが発生しているのが分かりますね。
このように、enumに似たデータ構造を比較的簡単に実装できるのが、Union型を使用する方法です。
オブジェクトリテラルを使用する
先ほどのUnion型と組み合わせて使う代替案として、オブジェクトリテラルを使用する方法もあります。
まずは、以下の例を見てみましょう。
【サンプルコード】
const Fruits = {
Apple: "Apple",
Peach: "Peach",
Orange: "Orange"
} as const
type Fruits = typeof Fruits[keyof typeof Fruits];
// type Fruit = "Apple" | "Peach" | "Orange" と同じ
function toJapanese(fruits: Fruits) {
switch (fruits) {
case Fruits.Apple:
return "りんご"
case Fruits.Peach:
return "もも"
case Fruits.Orange:
return "みかん"
}
}
console.log(toJapanese(Fruits.Apple));
【実行結果】
りんご
Union型のみの場合と比べると、コード量は増えてしまいますが、よりenumに近いコード形式となっています。
この方法を使う主なメリットの1つとして、値が変更になった場合の修正が行いやすい点が挙げられます。
例えば、上記の例をUnion型のみで記述した場合、以下のようなコードになります。
【サンプルコード】
type Fruits = "Apple" | "Peach" | "Orange";
function toJapanese(fruits: Fruits) {
switch (fruits) {
case "Apple":
return "りんご"
case "Peach":
return "もも"
case "Orange":
return "みかん"
}
}
console.log(toJapanese("Apple"));
このコードで一部の値を変更しようとした場合、型部分の記述だけでなく、switch文の条件なども修正する必要が出てきます。
定義した型を使用する箇所が多ければ多いほど、その手間も遥かに大きくなってしまうのです。
一方、オブジェクトリテラルを使用する方法であれば、比較などはキーで行えるため1箇所を修正するのみで済みます。
こうしたコード管理のしやすさは、大きな利点の1つです。
まとめ
今回は、TypeScriptにおけるenumについて解説を行いました。
上述したように、現在では非推奨になっているなど注意点が多い機能ですので、使用する場合には気を付けるようにしましょう。
TypeScriptの勉強方法は?
書籍やインターネットで学習する方法があります。昨今では、YouTubeなどの動画サイトやエンジニアのコミュニティサイトなども充実していて多くの情報が手に入ります。
そして、より効率的に知識・スキルを習得するには、知識をつけながら実際に手を動かしてみるなど、インプットとアウトプットを繰り返していくことが重要です。特に独学の場合は、有識者に質問ができたりフィードバックをもらえるような環境があると、理解度が深まるでしょう。
ただ、TypeScriptに限らず、ITスキルを身につける際、どうしても課題にぶつかってしまうことはありますよね。特に独学だと、わからない部分をプロに質問できる機会を確保しにくく、モチベーションが続きにくいという側面があります。独学でモチベーションを維持する自信がない人にはプログラミングスクールという手もあります。費用は掛かりますが、その分スキルを身につけやすいです。しっかりと知識・スキルを習得して実践に活かしたいという人はプログラミングスクールがおすすめです。
プログラミングスクールならテックマニアがおすすめ!

ITスキル需要の高まりとともにプログラミングスクールも増えました。しかし、どのスクールに通うべきか迷ってしまう人もいるでしょう。そんな方にはテックマニアをおすすめします!これまで多くのITエンジニアを育成・輩出してきたテックマニアでもプログラミングスクールを開講しています。
<テックマニアの特徴>
・たしかな育成実績と親身な教育 ~セカンドキャリアを全力支援~
・講師が現役エンジニア ~“本当”の開発ノウハウを直に学べる~
・専属講師が学習を徹底サポート ~「わからない」を徹底解決~
・実務ベースでスキルを習得 ~実践的な凝縮カリキュラム~
このような特徴を持つテックマニアはITエンジニアのスタートラインとして最適です。
話を聞きたい・詳しく知りたいという方はこちらからお気軽にお問い合わせください。