Masayan tech blog.

  1. ブログ記事一覧>
  2. TypeScriptで列挙型を実装するパターン3種類

TypeScriptで列挙型を実装するパターン3種類

公開日

環境

  • macOS Monterey 12.0.1
  • VSCode
  • node.js v18.6.0
  • npm 8.13.2
  • Typescript 4.6.4

TypeScript標準のEnumを使用する

実装手順

  • TypeScript標準のEnumをそのまま実装するだけ
  • 必要に応じて、実際に画面上に表示する際の名称を判定するlabel関数などを追加する
enum MenuType {
  Western = 1,
  Japanese = 2,
  Chinese = 3,
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace MenuType {
  export function label(menuType: MenuType) {
    switch (menuType) {
      case MenuType.Western:
        return '洋食'
      case MenuType.Japanese:
        return '和食'
      default:
        return '中華'
    }
  }
}
export default MenuType

メリット

  • 簡単に実装ができる
  • 言語標準機能である

デメリット

  • TypeScriptの列挙型(数値列挙型)は型安全性に欠ける
    • 数値列挙型の場合、数値から逆引きが可能だが、以下のように、存在しないメンバを指定してもエラーにはならず、undefinedが返却されてしまう
test('存在しないメンバ', () => {
  expect(MenuType[99]).toBe(undefined)
})

リテラル型とユニオン型の組み合わせ

実装手順

  • リテラル型でEnumのメンバのnameに相当する値とvalueに相当する値をそれぞれ定義し、入ることができる値を限定する
  • 必要に応じて、メソッドを追加する
    • nameからvalueを取得する関数
    • 実際に画面上に表示する際の名称を判定する関数
    • valueからnameを逆引きする関数
type FighterType = 'Boxer' | 'Mma' | 'Other'
type FighterTypeValue = 1 | 2 | 9

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace FighterType {
  export const fighterTypeOf = (type: FighterType): FighterTypeValue => {
    if (type === "Boxer") {
      return 1
    } else if(type === "Mma") {
      return 2
    } else {
      return 9
    }
  }

  export const fighterTypeLabelOf = (type: FighterType): string => {
    if (type === "Boxer") {
      return "ボクサー"
    } else if(type === "Mma") {
      return "総合格闘家"
    } else {
      return "そのほか"
    }
  }

  export const fighterTypeValueOf = (value: FighterTypeValue): FighterType => {
    if (value === 1) {
      return "Boxer"
    } else if(value === 2) {
      return "Mma"
    } else {
      return "Other"
    }
  }
}

export default FighterType

メリット

  • 比較的簡単に実装ができる
  • 標準のEnumと異なり、逆引きの際に存在しない値が指定されることを防止できる

デメリット

特になし

オブジェクトリテラル

実装手順

  • enumのメンバの仕様に合わせるため、const assertionでオブジェクトの全プロパティをreadonlyにして変更できないようにする
  • typeofでProductTypeオブジェクトの型情報を生成する
  • keyofで2.のProductTypeの型情報から、プロパティのkey名をオブジェクトリテラルで取得("Book" | "Groceries" | "Other")
  • ProductTypeオブジェクトに添字で、3を指定すると、各プロパティの値(1,2,9)が取得でき、それをtypeofで指定すると、1 | 2 | 9 が得られる
export const ProductType = {
  Book: 1,
  Groceries: 2,
  Other: 9,
} as const

type ProductTypeAlias = typeof ProductType[keyof typeof ProductType]

export function productTypeKeyOf(productType: ProductTypeAlias) {
  switch (productType) {
    case ProductType.Book:
      return 'Book'
    case ProductType.Groceries:
      return 'Groceries'
    default:
      return 'Other'
  }
}

export function productTypeLabelOf(productType: ProductTypeAlias) {
  switch (productType) {
    case ProductType.Book:
      return '本'
    case ProductType.Groceries:
      return '食料品'
    default:
      return 'その他'
  }
}

メリット

  • オブジェクトリテラルを使用しているので、Enumのメンバに近い実装ができる
  • 標準のEnumと異なり、逆引きの際に存在しない値が指定されることを防止できる

デメリット

  • リテラル型とユニオン型の組み合わせの方法よりも、やや実装が複雑になる

まとめ

いかがでしたでしょうか。本記事では、本記事ではTypeScriptで列挙型を実装するパターン3種類を紹介しています。TypeScript標準のEnumを使用する方法、リテラル型とユニオン型の組み合わせる方法、オブジェクトリテラルを使用する方法について、それぞれ具体例とメリットデメリットを挙げながら具体的に紹介しています。