TypeScriptでユニオン型と配列を1箇所で管理する
TypeScriptでユニオン型を定義し、その値を含む配列を作成する場合、よく以下のようなコードを書くことがあります。
type Taste = 'しょうゆ' | 'みそ' | 'とんこつ'
const tastes = ['しょうゆ', 'みそ', 'とんこつ']
この方法は一見シンプルに見えますが、問題があります。新しい味を追加したり、既存の味を変更する場合、ユニオン型の定義と配列の両方を更新する必要があり、2箇所を直す手間が発生します。これはミスを招きやすく、メンテナンス性もよくありません。
解決策: as constとtypeofを使ったシンプルな方法
この問題を解決するために、as const
とtypeof
を使うことで、定義を1箇所にまとめることができます。
const tastes = ['しょうゆ', 'みそ', 'とんこつ'] as const
type Taste = (typeof tastes)[number]
これで、tastes配列を更新するだけで、Taste型も自動的に変更されます。ユニオン型を手動で管理する必要がなくなるため、コードの保守が簡単になります。
TypeScriptの原則として「型に関する記載は実行時のコードに影響を与えることができない」というのがあるので諦めていたのですが、この逆の「実行時のコードを元にして型を定義する」というのは可能なので、これを使ってリストを一ヶ所にまとめることができます。すごい!
as const
はTypeScriptの機能で、これをつけると変数が読み取り専用になり、変更されないことが保証されます。これを文字列の配列につけることで、単純な配列型ではなく中身まで含めた型として解釈され、その要素もユニオン型だと認識されます。
なぜこの方法が優れているのか?
-
定義の一元化:
配列 tastes の定義がユニオン型 Taste の元になっているので、リストを一度書けばよいだけです。これにより、変更が必要なときも1箇所の修正で済みます。 -
型安全:
as const によって、TypeScriptは tastes 配列をリテラル型として扱い、型が文字列そのものになります。これにより、ユニオン型が配列と常に一致し、間違いを防ぐことができます。 -
メンテナンスが容易:
配列を更新するだけで、型も正しく更新されるため、コードのメンテナンスがシンプルでエラーが少なくなります。
使用例
この方法を使うことで、例えば関数の引数として Taste 型を使う場合も、常に配列 tastes と一致した値だけが受け取られるようになります。
const describeTaste = (taste: Taste) => {
switch (taste) {
case 'しょうゆ':
return 'しょっぱい醤油味'
case 'みそ':
return 'コクのある味噌味'
case 'とんこつ':
return 'クリーミーな豚骨味'
default:
return '不明な味'
}
}
const allTastes: Taste[] = tastes // これも安全に型が保証されます!
まとめ
as const
とtypeof
を使うことで、TypeScriptのユニオン型と配列をよりメンテナブルに管理することができます。特に多くの値を扱う際や、頻繁に値が追加・変更される場合、この方法を使うことで大幅にコーディングの負担を軽減できます。今後のプロジェクトでぜひ試してみてください!
Discussion