Literal Union の検証
ユニオンタイプは多分Typescriptで一番よく使われるタイプの一つだと思います。色んなタイプを組み合わせてマージされた形ですが、特定の変数が色んな形のタイプを持つ場合、便利に使うことができます。
特に、Mapped Typeを定義する時、keyのタイプをユニオンで指定することはよくあることです。
ただ、このユニオンタイプには一つの問題があり、それは特定の変数が特定のユニオンに属するかどうかを直接的に検証することができないということです。
しかし、もしユニオンがリテラル型で構成されている場合、as const を使ってこの問題を解決することができます。
type Fruit = 'apple', 'banana', 'grape';
このようなユニオンがあるとする。
まず、このユニオンに属するリテラル型を配列にします。 そして、この配列の後ろに as const を付けて、要素を read-only な状態にします。
const fruitList = ['apple', 'banana', 'grape'] as const;
完成された配列を持ってlookup typeを使ってユニオンを新しく定義することができます。lookup typeが分からない場合はこのリンクを参考してください。
const fruitList = ['apple', 'banana', 'grape'] as const;
type Fruit = typeof fruitList[number];
これで fruitList
を利用して type guard を実装できるようになりました。
function isFruit(val: any): val is Fruit {
if (fruitList.includes(val)) return true;
return false;
}
Object Literal での活用
下記のような object があるとします。
const capitals = {
japan: 'tokyo',
korea: 'seoul',
china: 'beijing',
}
この object の value を引数として受ける関数を作りたい場合はどうすればいいでしょう。
const capitals = {
japan: 'tokyo',
korea: 'seoul',
china: 'beijing',
}
function goAbroad(
from: 'tokyo' | 'seoul' | 'beijing',
to: 'tokyo' | 'seoul' | 'beijing'): string {
...
}
このように一つ一つ書く方法もありますが、この方法があまり満足できないことは誰もが知っているでしょう。
ここでも as const が輝きます。
const capitals = {
japan: 'tokyo',
korea: 'seoul',
china: 'beijing',
} as const
type Capital = typeof capitals[keyof typeof capitals]
// Capital === 'japan' | 'korea' | 'china'
function goAbroad(from: Capital, to: Capital): string {
...
}
どういうことが起こっているのか説明しますと、まずtypeof capitalsは次のようなタイプを返すことになります。
type typeOfCapitals = typeof capitals;
// type typeOfCapitals = {
// readonly japan: "tokyo";
// readonly korea: "seoul";
// readonly china: "beijing";
// }
ここに keyof をつけると、次のような値が得られます。
type keyOfTypeOfCapitals = keyof typeof capitals;
// type keyOfTypeOfCapitals = 'japan' | 'korea' | 'china'
これで、上で説明した lookup type を利用した方法で再びユニオンを作ることができます。
type keyOfTypeOfCapitals = keyof typeof capitals;
type Capital = typeof Capital[keyOfTypeOfCapitals];
// Capital === 'tokyo' | 'seoul' | 'beijing'