abeshi blog
カテゴリーで検索

enum型よりunion型を

2022年1月10日



はじめに



最近TypeScriptのenum型が読みづらく、どうにかならないかと思うことが多々ありました、、
実際TypeScriptプログラマーは最近enum型よりUnion型を好む傾向にあるそうです。


enum型とは


enum Genders {
  male = "MALE",
  female =  "FEMALE",
  notSpecified = "NOT_SPECIFIED"
}



const gender: Genders = Genders.male; // "MALE"


Union型だとどうなるか


type Genders = "MALE" | "FEMALE" | "NOT_SPECIFIED"
const gender: Genders = "MALE" // MALE



純粋にこちらの方が読みやすい。
しかも型補完もしっかりと出てくれます!


ただこれだけではenum型を使わない理由になりません。


なぜenum型はつかはない方がよいのか?


数値のenumの場合は型安全にならない


enum Genders {
  male, // 0
  female, // 1
  notSpecified // 2
}

const gender: Genders = Genders.male; // OK
const gender2: Genders = 0; // OK???????



このように予期せぬ値が入る可能性があります。

その他にもenumはTypeScriptの”a typed superset of JavaScript”に反するといったこともあるそうです。
下記の記事に非常に詳しく書かれておりました!
https://www.kabuku.co.jp/developers/good-bye-typescript-enum


まーようするに冗長な書き方になる可能性があるのと、型安全でなくなる可能性がある為、Union型を使っておいた方が無難ということになります。


const assertionを使うとenumを使わずにUnion型で代用できる


export const genders = {
  male: "MALE",
  female: "FEMALE",
  notSpecified: "NOT_SPECIFIED"
} as const




const でgendersを普通に定義します。

console.log(genders.male) // "MALE"




as const をつけることで




readonlyがつき、型安全になっていることがわかると思います。
つまり定数になっているってことですね。


ここからUnion型を生成できます。

export type Genders = typeof genders[keyof typeof genders]; // "MALE" | "FEMALE" | "NOT_SPECIFIED"



これでkeyとvalueを定義しつつ、安全にUnion型を生成することができます。
上記がどういった挙動になっているかというと、

const Options = {
  a: "AA",
  b: "BB",
  c: "CC",
} as const;


// 以下の2つは同じ意味。型は"AA"として解決される。
type A1 = typeof Options.a;
type A2 = typeof Options["a"];


// 添字にはUNION型が使える。型は"AA"|"BB"として解決される。
type B = typeof Options["a" | "b"];


// オブジェクトのフィールド名をUNION型として取得する。型は"a"|"b"|"c"として解決される。
type C = keyof typeof Options;


// BとCの方法を組み合わせたもの。型は"AA"|"BB"|"CC"として解決される。
type D = typeof Options[keyof typeof Options];



このようにkeyofとtypeofが使われております。


labelなどで性別を表示したい場合はenum同様switch分を使って表現することができます。

// - 型定義
import { genders, Genders } from "../types/User";

export const formatterStrings = {
  gender: function (gender: Genders) {
    switch (gender) {
      case genders.male: return "男";
      case genders.female: return "女";
      case genders.notSpecified: return "指定しない";
    }
  }
};


return Object.values(genders).map((value: Genders) => ({
  label: formatterStrings.gender(value),
  value
}))



お疲れさまでした!