[TypeScript] 列挙型は Union か enum か

TypeScript で列挙型を作りたい場合、リテラル値の Union か enum を使うと思います。

// Union
type T1 = 'A' | 'B';

// enum
enum T2 {
    A,
    B
}

(「リテラル値の Union」という表現が正しいかどうかはわからない…)

Union の用途は列挙型だけではありませんが、上記のように文字列や数値の Union は列挙型として用いる事も出来るため enum との使い分けが難しいです。それらの違いについて意外と日本語の記事がなさそうだったのでまとめて考察してみました。

まず結論

普通に使うならどっちでも良い。

以下、考察の結果を書いていきます。

代入・比較

それぞれの型の変数への代入と比較は以下のようにしてできます。

// Union
type T1 = 'A' | 'B';

const t1: T1 = 'A';
if (t1 === 'A') {
    //
}

// enum
enum T2 {
    A,
    B
}

const t2: T2 = T2.A;
if (t2 === T2.A) {
    //
}

enum は必ず 型名.プロパティ の形にする必要がありますが、Union の方は必要ありません。値だけで OK です。

また Union の方は文字列で比較しているだけなので、値が変わった時のことを考えると若干不安を覚えますが、しっかりコンパイルエラーになってくれます。

type T1 = 'C' | 'B';  // A がなくなった

const t1: T1 = 'A';   // コンパイルエラー:Type '"A"' is not assignable to type 'T1'.
if (t1 === 'A') {     // コンパイルエラー:This condition will always return 'false' since the types 'T1' and '"A"' have no overlap.
    //
}

生成されるJSがちがう

TypeScript で Union 型を定義しても JS は生成されません。

一方、enum を定義すると以下のような JS が生成されます。

// TypeScript
// enum T2 {
//     A,
//     B
// }

// 生成された Javascript
var T2;
(function (T2) {
    T2[T2["A"] = 0] = "A";
    T2[T2["B"] = 1] = "B";
})(T2 || (T2 = {}));

この 3 行目について見ていきましょう。

T2[T2["A"] = 0] = "A";

T2["A"] = 0 はもちろん T2 オブジェクトに A プロパティを生やして 0 を代入しています。

一方、 Javascript の代入演算子は代入した値を返す ため、式 T2["A"] = 00 を返します。なので T2[T2["A"] = 0] = "A"T2[0] = "A" と等価になります。

従って、enum T2 はプロパティ A と添字 0 の両方でアクセスできます。

T2.A    // 0
T2["A"] // 0

T2[0]   // A

これが意味することは

Union は完全に TypeScript の世界の話であり、enum は Javascript のオブジェクトとして具体化されます。

なので、enum は以下のように普通のオブジェクトとして扱うことが出来ます。

for (let t2 in T2) {
    console.log(t2);
}
// 0
// 1
// A
// B

まあ enum をループしたいケースはほとんどないと思いますが・・・

フラグとしてつかうなら enum

数値の enum だけの話になってしまいますが、 数値 enum は n ビットのフラグとして使えます。

enum T2 {
    A = 0,
    B = 1 << 0,
    C = 1 << 1,
    D = 1 << 2,
}

const flags = T2.B | T2.D; // 5

if (flags & T2.B) {
    // ここは通る
}

if (flags & T2.C) {
    // ここは通らない
}

上記例の場合 flags 変数は3ビットのフラグ変数です。BフラグとDフラグが立っています。

Bフラグが立っているため、flags & T2.B1 << 0 つまり 1 を返します。また、Cフラグは立っていないため flags & T2.C0 を返します。

まとめ

「Union は完全に TypeScript だけの話。enum は普通のオブジェクトに展開される」ことさえ覚えておけば後は好みでいいんじゃないでしょうか。

リテラル値の Union はシンプルに記述できる反面、コードによっては可読性が落ちることにも繋がります。バランスをみて良さそうな方をチョイスしましょう。

参考

Typescript has unions, so are enums redundant? – Stack Overflow
Enums – TypeScript Deep Dive

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です