1分でわかるTypeScriptでenumを使ってはいけない理由と代替案

TypeScriptでenumを使ってはいけない主な3つの理由

1. 存在しない場合でも警告が出ない

TypeScript
enum Mode {
  Auto,
  UDP,
  TCP,
}

console.log(Mode[0]) // "Auto"
console.log(Mode[4]) // undefined(警告が出ない)

2. コンパイル後の構造が複雑化する

TypeScript
enum Mode {
  Auto,
  UDP,
  TCP,
}

// コンパイル後は以下のコードになる
{
  0: "Auto",
  1: "UDP",
  2: "TCP",
  Auto: 0,
  UDP: 1,
  TCP: 2
}

3. 予期せぬ型の混合アクセスが可能

TypeScript
// コンパイル後のコードが前述のようになっているため以下の結果になる
console.log(Mode.Auto)     // 0
console.log(Mode['Auto'])  // 0
console.log(Mode[0])       // Auto

enumの代替案

オブジェクトで生成する(キーと値が異なる場合)

TypeScript
const Mode = {
  Auto: 'Auto1',
  UDP: 'UDP2',
  TCP: 'TCP3',
} as const satisfies Record<string, string>

console.log(Mode.UDP)
// UDP2

console.log(Mode[0])
// undefined(警告が出る)

console.log(Object.values(Mode).at(0))
// Auto1

console.log(Object.values(Mode).at(-1))
// TCP3

オブジェクトを関数で生成する(キーと値が同じ場合)

TypeScript
function createEnum<T extends readonly string[]>(keys: T) {
  return Object.fromEntries(keys.map(k => [k, k])) as { [K in T[number]]: K }
}

const Mode = createEnum(['Auto', 'UDP', 'TCP'] as const)
type Mode = typeof Mode[keyof typeof Mode]

console.log(Mode.UDP)
// UDP

console.log(Mode[0])
// undefined(警告が出る)

console.log(Object.values(Mode).at(0))
// Auto

console.log(Object.values(Mode).at(-1))
// TCP