技術とかの雑なToday I Learnedメモ

TypeScriptのenumを使わない

TypeScriptのenumを使わない

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

TypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介します - LINE ENGINEERING

enumが使われているコードがあったので修正したいと思い調べた(昨日as constを調べたのもその一環)

挙げた記事は多少流し読みしたことがあったのだけど、今回ちゃんと読んだ。

enum

そもそもまず以下の書き方を知らなかった。

// 指定しない場合は0から順番に数字が振られる
enum NumEnum {
  Aaa,
  Bbb,
  Ccc
}

const hoge: NumEnum = NumEnum.Aaa // => 0
const fuga: NumEnum = NumEnum.Bbb // => 1
const piyo: NumEnum = NumEnum.Ccc // => 2

// const enumというのもある
const enum ConstEnum {
  Aaa,
  Bbb,
  Ccc
}

コンパイル後のコードとか今まで全然思いを馳せてなかったので参考になった。

var NumEnum;
(function (NumEnum) {
  NumEnum[NumEnum["Aaa"] = 0] = "Aaa";
  NumEnum[NumEnum["Bbb"] = 1] = "Bbb";
  NumEnum[NumEnum["Ccc"] = 2] = "Ccc";
})(NumEnum || (NumEnum = {}));
const hoge = NumEnum.Aaa;
const fuga = NumEnum.Bbb;
const piyo = NumEnum.Ccc;

難しいけど、即時関数の引数にNumEnum || (NumEnum = {})という値を渡していて、これはNumEnumがfalsyなら空のオブジェクトを渡す、ということだろうか。

NumEnum[NumEnum["Aaa"] = 0] = "Aaa";

めちゃくちゃ見慣れない書き方で何やってるのか全然分からん……。 devtoolsのconsoleで見てみたらこうなってた。

> NumberEnum
// => {0: 'Foo', 1: 'Bar', 2: 'Baz', Foo: 0, Bar: 1, Baz: 2}

どういうこと?プロパティアクセスの[]の内部で式を定義して、そこでプロパティの値を入れているなら別々にやればいいのでは?と思ったけどそういうわけにもいかないのかな。難しい。

Unionに置き換える

enumをUnion Typesに置き換えることでenumを使わないようにできる。

const color = ['red', 'green', 'blue'] as const
type Color = typeof color[number] // => 'red' | 'green' | 'blue'

数字ではなく名前を指定する場合も簡単にできる。

const color = {
  red: 'Red',
  green: 'Green',
  blue: 'Blue'
} as const
type Color = typeof color[keyof typeof color] // => 'Red' | 'Green' | 'Blue'
const red: Color = color.red // => 'Red'

enumの不透明性はUnion Typesでは実現できない。

文字列のenumを使うとき、ある文字列や文字列リテラルがenumに含まれることが文脈的に明らかであったとしても、それらをenumに割り当てることはできません。

enum StringEnum {
  Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!

(上記コードは記事内から引用)

この例はとても分かりやすい。

enumのデメリット

結構色々あるんだなと思ったけど、直感的で分かりやすいのは

  • constでないenumはTypeScriptの”a typed superset of JavaScript”というコンセプトにそぐわない
  • 数値のenumは型安全ではない
  • 文字列のenumの宣言は冗長になることがある

だった。

ESLintで縛る

typescript-eslintのno-restricted-syntaxというルールでenumを使うと怒ってくれるらしい。

{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "TSEnumDeclaration",
        "message": "Don't declare enums"
      }
    ]
  }
}

これ使います。