【TypeScript】enum, union, as constって意思を持って使い分けてる?
TypeScriptで、UIや挙動を切り替える時に使うキーを複数の値を一つにまとめたい事ってそこそこあると思うんですが、何で定義するのがいいんでしょうか?
パッと思い付くのはenum, ユニオン型, as constオブジェクトの3つですが、正直それぞれを明確な意思を持って選べていなかったので今回比較してみることにしました。
結論
楽したいならユニオン型、幅広いケースに対応したいならas constオブジェクトを使うのが良さそう
前提
今回は全てのTypeScriptの記法でこれらを使うべき/べきでないを論じるつもりはありません。あくまで複数の数値や文字列をひとまとまりとして表したい場合にどれを選ぶべきかを検討するという前提で考えます。
enum
enum Person {
AMY
ELMA
}
正直これが一番無いだろうなと思います。
ご存知の通りTypeScriptのenum(列挙型)には問題があります。ここで詳しく解説するつもりはありませんが、公称型であることや型安全性に多少の不安があることから積極的に選ぶことはないと思います。
個人的にはTypeScriptのトップページにも書いてあるとおり「TypeScript is JavaScript with syntax for types.」であり続けて欲しいので、なるべく使いたくないです。
即時実行関数にトランスパイルされるってなんやねんという気持ちになりますね。
ただ逆に言えば絶対に選んではいけないほどのクリティカルな理由がないとも言えます。enumを選んだからといってメジャーなユースケースで困るようなことはそこまで無さそうです。
補足:いつかJSにenumが来るかも
先日TSKaigiに参加した際にtc39のenumに関するproposalが動いているという話を聞きました。
リポジトリを見ると7年前から存在していますし、現状でもstage1らしいので実装はまだ先だと思いますが、将来型安全で標準実装のenumを堂々と使える日が来るかもしれません。
↓最近公開されていたTSKaigiのLT動画
正直ここからが本番です。今回の主な比較対象はenumを除いた二つでずっと考えていました。
それぞれのメリットデメリットを比較して考えていこうと思います。
ユニオン型
type Song = "AMY" | "ELMA"
TypeScriptのド定番な書き方ですね。
メリット
- 記述量が少ない:1行書くだけで簡単
- トランスパイルすると消える:跡形も無くなるからバンドルサイズが軽くなる
デメリット
- 柔軟な操作は難しい:キーを配列で取ったりする様なのは難しい
- どの型か分かりにくい:実装の中で使われている文字列リテラルだけを見て、それがどの型の値なのかを識別するのが難しい
as constオブジェクト
const Song = {
Amy: "AMY",
Elma: "ELMA"
} as const
type Song = (typeof Song)[keyof typeof Song]
こちらはas constでアサーションをするパターンです。オブジェクトを定義するだけだと不便なのでその値を元に別途型をユニオン型を作成しています。
メリット
- 柔軟に使える:Object.keysなどで色々できる
- 型の判別がしやすい:実装中の値を見てすぐにどの型のものかが分かる
- 一括変換で便利:エディタの機能を使って値を一括で変換できる
- 定義ジャンプできる:使用箇所から定義元に飛ベて嬉しい
デメリット
- 冗長:シンプルに行数も多いし、同じような値を毎回2回書かなきゃいけないのが面倒。型も別で定義する必要がある。
- バンドルサイズが大きくなる:トランスパイルしてもオブジェクト自体は残ってしまう
結局どれがいいのか
色々調べたり検証したりした結果、楽したいならユニオン型、幅広いケースに対応したいならas constオブジェクトを使うべきという結論に至りました。
幅広いパターンの例
- as constオブジェクトのメリットで挙げた要素が欲しい
- キーを配列で取得してそれら全てに対して処理を行いたい
最初は結構as constオブジェクト寄りの考えを持っていたのですが、調べていく内に「あれ、これ別にユニオン型でもそんなに困らないぞ?」となる場面が本当に多かったです。
型補完や変更容易性に関してもユニオン型でかなりカバーできました。正直最初の方は「ハードコーディングなんかしないに越したことないからなぁ〜!」という安直な考えだったので今回ちゃんと考えてみてよかったです。
ただ、今でもas const派閥ではあります。というのも、やはり定義ジャンプできたりどの型の内容なのかが一目で分かったり一括変換ができるというのは認知負荷も下がりますしストレスも減ります。
その一方で、最初の定義が結構面倒だという意見も分かります。ユニオン型なら1行で済むものを何行にも分けて書くのはコストだなぁとは思うので。
僕が感じているそれらの恩恵が定義のコストに見合っているかは正直微妙なところで、実際のユースケースや個々人のお気持ちによるところが大きいというのが正直なところだと感じました。
例えばgraphql-codegenの自動生成ファイルの様に、開発者のコスト0でas constオブジェクトが使用できる場合は積極的に使っていくのが良さそうですが、通常の利用方法の場合は皆さんの好きな方を使えばいいと思います!
宣伝
これを考えるきっかけになったLTを7/28のお昼にやるので見てね!
Discussion