【TypeScript】enumはなぜ非推奨?代替手段は?

更新日:2022/11/21

TypeScriptのenum型は非推奨らしいです。
なぜ非推奨なのでしょうか。
代わりの方法はあるのでしょうか。

 

非推奨の理由

enumが非推奨だと言い切っている人が時々います。
しかし使う使わないは個々の考えやチームの方針で判断するものなので、無条件に使用を控える必要はないです。

ただしそれなりの理由があるので、その理由を理解して enumを使用するかどうかを判断しましょう。

使わない方がいいと言われている理由をいくつか挙げてみます。

数値チェックしていない

enum型は数値の型チェックしてくれません。
次のように範囲外の数値を代入してもエラーにならないのです。

enum Status { BUSSY, ACTIVE,STOP };
let stat:Statu1;
stat = Status.ACTIVE;
stat = 1;          // 範囲内はエラーではない
stat = 10000;  // 範囲外もエラーではない

TypeScriptは型チェックすることが存在意義(いいすぎ?)です。
だから、次のような enum型を使った条件判定をしがちです。

const func = (stat:Status)=>{
  switch(stat){
    case Status.BUSSY:
        /* 処理 */ return;
    case Status.ACTIVE:
        /* 処理 */ return;
    case Status.STOP:
        /* 処理 */ return;
  }
}

JavaScriptなら型チェックしてくれないので、プログラマーはdefaultを記述します。
しかし、TypeScriptは型チェックしてくれるからこれで十分だと判断しがちです。たぶん。

実際には、次のように範囲外の数値を指定しても、トランスパイルでエラーになりません。

func(1000);

想定外のバグを生み出す結果となりますね。

ちなみに、次のような配列形式の代入でエラーにならないのはany型だから。

const s:Status = Status["abc"];

noImplicitAnyオプションを有効にすることで、次のエラーが表示されます。
「Element implicitly has an 'any' type because index expression is not of type 'number'.」

オプションで生成されたコードが大きく変わる

const enum は preserveConstEnumsオプションを有効にすると、enumと同じ扱いになります。
他にもオンオフでコードの解釈が変わるオプションがありますが、preserveConstEnumsオプションは場合によっては大幅な改修が必要なレベルです。

個人的にプログラム開発しているならどうでもいいすが、チームなら致命的です。
オプションの扱いを周知徹底するのは不可能だから、enumを使用禁止にしたほうが無難。

Tree-shaking

webpackなどのバンドラーには、使用していないコードを削除するTree-shakingという機能があります。
enum型は挿入されたコードそのものが、既に使用されているとみなされるので削除対象になりません。

enum型は次のようなコードを生成します。

var Status;   // ← 下の即時関数で、Statusに値をセットしている
                   //      つまり使っている
(function (Status) {  // 即時に実行されるんだから、使っている
    Status[Status["BUSSY"] = 0] = "BUSSY";
    Status[Status["ACTIVE"] = 1] = "ACTIVE";
    Status[Status["STOP"] = 2] = "STOP";
})(Status || (Status = {}));

即時関数は、定義と同時に実行される関数なので削除できまないのです。

個人的はTypeScript側の問題ではないような気がしますが、enumに関する大きな問題だと判断している人が多いようですね。

よくわからないコードを挿入してほしくない

前項で記載していますが、enumはよくわからない(いや、わかるけど!)コードを挿入します。

JavaScriptから始めたプログラマー目線では、TypeScriptは型チェックだけやっていればいいのです。
「よくわからないコードを挿入するってどうなんだ?」言いたくなります。

さらに「これだからTypeScript嫌なんだよ」と、TypeScript批判につながって精神的によくないですね。

 

代替手段1: union型

enum型の代替手段として挙げられるのが、union型です。
リテラル値でunion型を定義することで、値を制限することができます。

前項の数値チェックしていないのコードを union型に置き換えてみます。

type Status = 0 | 1 | 2;

const func = (stat:Status)=>{
  switch(stat){
    case 0:  // BUSSY
        /* 処理 */ return;
    case 1:  // ACTIVE
        /* 処理 */ return;
    case 2:  // STOP
        /* 処理 */ return;
  }
}
func(0);        // エラーではない
func(10000); // エラー: Argument of type '10000' is not assignable to parameter of type 'Status'.

union型で、0、1、2に制限しているので、それ以外の値はエラーになっています。
しかし、各数値に意味付けできていないので、コメントがないと何をやっているのかわかりませんね。

そこで、文字列のunion型に変更します。

type Status = "BUSSY" | "ACTIVE" | "STOP";

const func = (stat:Status)=>{
  if( stat === "BUSSYY"){} // エラー: This condition will always return 'false' since the types 'Status' and '"BUSSYY"' have no overlap.
  switch(stat){
    case "BUSSY":
        /* 処理 */ return;
    case "ACTIVE":
        /* 処理 */ return;
    case "STOP":
        /* 処理 */ return;
  }
}

func("STOP    "); // エラー: Argument of type '"STOP    "' is not assignable to parameter of type 'Status'.

文字列はタイプミスしやすいですが、チェックしてくれるので安心です。

ただし、一つ問題があります。

次のように union型の内容を変更した場合は、コード上の該当部分を全て変更する必要があります。

type Status = "BUSY" | "ACTIVE" | "STOP";

ビジーは英語で bussyだと思ったら、busyだった!
はずかしい。
即刻証拠隠滅が必要ですね。

でも、めんどくさいですね。

 

代替手段2: as const + union型

union型の内容を変更したら修正が面倒という問題に、ほんの少しだけ解決できるのが、as const + union型です。

次のようなコードです。

const Status ={ 
  BUSSY:"BUSSY", ACTIVE:"ACTIVE", STOP:"STOP"
} as const;

type Status = typeof Status[keyof typeof Status];

const func = (stat:Status)=>{
  switch(stat){
    case Status.BUSSY:
        /* 処理 */ return;
    case Status.ACTIVE:
        /* 処理 */ return;
    case Status.STOP:
        /* 処理 */ return;
  }
}
func(Status.BUSSY);

プロパティ名と値が同じ文字って意味あるの?数値でいいのでは?、いやいやそれでいいんだよ、とかいろいろな意見がありますね。主題とは関係ないので置いておきます。

最初の as const は、オブジェクトのプロパティ型をプロパティ値のリテラル型にします。

少しだけ違うのですが、次のように置き換えられます。

const Status:{ 
  BUSSY:"BUSSY", ACTIVE:"ACTIVE", STOP:"STOP"
} ={ 
  BUSSY:"BUSSY", ACTIVE:"ACTIVE", STOP:"STOP"
};

as constについては、次のページを読んでみてください。

続く type Status = typeof~の行は、 "BUSSY" | "ACTIVE" | "STOP" という union型を生成しています。

typeof と keyof については、次のページを読んでみてください。

コード上では Status が二つ定義されています。

最初の const は、オブジェクトなので値です。
次の type は、型です。

値と型は使用できる場所が決まっているので、TypeScriptは見分けることができています。

そして気が付きました。
ビジーは英語で bussyだと思ったら、busyだった!
はずかしい。
即刻証拠隠滅が必要ですね。

でも今回は簡単です。
2行目の "BUSSY" を "BUSY" に変えるだけです。
プロパティ形式で値を渡していれば、他を変更する必要がありません。

const Status ={   // "BUSSY" を "BUSY" に変更
  BUSSY:"BUSY", ACTIVE:"ACTIVE", STOP:"STOP"
} as const;

type Status = typeof Status[keyof typeof Status];

const func = (stat:Status)=>{
  switch(stat){
    case Status.BUSSY:
        /* 処理 */ return;
    case Status.ACTIVE:
        /* 処理 */ return;
    case Status.STOP:
        /* 処理 */ return;
  }
}
func(Status.BUSSY);

プロパティ名も BUSSY だったよ…
こちらは変更しないとダメだった…

更新日:2022/11/21

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。