TypeScriptでenumを使いたいんじゃ
先日「TypeScriptでenumを定義したい!」ということがあり、その時に調べたことを整理しながらアウトプットしたいと思います。
とりあえず、enumは下記のように定義することができます。
enum Position {
Top,
Right,
Bottom,
Left,
}
ですが、調べてみるとどうやらこの記述には問題点があるそうです。
その問題点と解決方法を、こちらをもとに整理したいと思います。(いつも参考にさせてもらってます。ありがとうございます。ビッグラヴ)
問題点
1. 数値列挙型には型安全上の問題がある
数値列挙型とは、下記のように数値をenumで定義することです。
enum ZeroOrOne {
Zero = 0,
One = 1,
}
この数値列挙型、実はnumber型ならなんでも代入できてしまうという問題があるのです。
なので、上記のように0か1のみのenumを定義したつもりでも、それ以外の数値が代入できちゃいます。困ったもんだ。
そして、enumは下記のようにオブジェクト型でアクセスすると値が取得できます。
console.log(ZeroOrOne[0])
// => 'Zero'
これの何が問題なのかというと、存在しない値でアクセスしてもエラーが起きないことです。
console.log(ZeroOrOne[9])
// => undefined
…😫😫
2. 文字列列挙型だけ公称型になる
型システムには公称型と構造的部分型の二種類があり、TypeScriptは後者の構造的部分型を採用しています。
この2つの違いはこちらの記事を参考にさせていただきました。(これもsuinさんの記事)
公称型:2つのオブジェクトが全く同じプロパティを持っていても、継承関係が無ければ代入できない。(Java, C++など)
構造的部分型:2つのオブジェクトが全く同じプロパティを持っている場合、そのオブジェクトに代入できる。(Go, TypeScriptなど)
先程も書いたようにTypeScriptは構造的部分型を採用しているのですが、文字列列挙型(文字列を定義したenum)は例外的に公称型となるそうです。
ちなみに数値列挙型は公称型にはなりません。なぜなんだろうか…🤔
このようにばらつきが出てしまうのも問題とされています。
3. 呼びされない不要なコードがコンパイルされる
TypeScriptで定義されたenumは、即時関数としてコンパイルされます。
即時関数でコンパイルされると、Tree-shakingというwebpackなどでバンドルする際に実行されないコードを削除してくれる機能がうまく動作しないという問題があるそうです。
解決方法
1. union型を使う
union型とは、「いずれかの型」を表すものです。
この場合、Hoge型にはstring型かnumber型かundefined型が入ります。
type Hoge = string | number | undefined
enumとして使用する場合は下記のようになります。
type FrameWork = 'React' | 'Vue' | 'Angular'
type Props = {
name: FrameWork
}
const Frontend = ({name}: Props) => {
return (
<p>{name}</p>
)
}
// 呼び出し
<Frontend name={'React'} />
こうすることで、name
にはFrameWork型で定義されている3つの値しか指定できなくなります。
2. オブジェクトリテラルを使う
オブジェクトで型の値を定義します。
そのオブジェクトのプロパティをas const
を使ってReadOnlyにし、さらにkeyof typeof
を使って型として定義します。
keyof typeof
についてはこちらの記事が分かりやすかったです🙌
const FrameWork = {
React: 'React',
Vue: 'Vue',
Angular: 'Angular'
} as const
type FrameWork = typeof FrameWork[keyof typeof FrameWork]
type Props = {
name: FrameWork
}
const Frontend = ({name}: Props) => {
return (
<p>{name}</p>
)
}
// 呼び出し
<Frontend name={FrameWork.React} />
まとめ
enumは特に理由が無い場合使わない方が良さそうです。
他にもこんな方法があるよ〜などありましたら教えて下さい😌