💭
TypeScript で値が Union Type にマッチするかを検証したい
TypeScript version: v4.3.5
元々やりたかったのは以下のようなこと。外部入力の文字列を Union Type にマッチするか検証してマッチしなければデフォルト値を返すみたいなやつ。
const colors = ["red", "blue", "yellow"] as const;
type Color = typeof colors[number];
const defaultColor = colors[0];
function toColor(color: string): Color {
return colors.includes(color) ? color : defaultColor; // Type 'string' is not assignable to type '"red" | "blue" | "yellow"'.
}
const color = toColor(localStorage.getItem("color") || "");
しかし上記のコードはエラー。気持ち的には colors.includes(color)
が真なら color
は Color
と判定されてほしいけどそこまで賢くはなかった。(playground)
Type Guard 使ってこういう感じにしてみる。
const colors = ["red", "blue", "yellow"] as const;
type Color = typeof colors[number];
const defaultColor = colors[0];
function isColor(color: string): color is Color {
return colors.includes(color); // Argument of type 'string' is not assignable to parameter of type '"red" | "blue" | "yellow"'
}
function toColor(color: string): Color {
return isColor(color) ? color : defaultColor;
}
const color = toColor(localStorage.getItem("color") || "");
しかしこれもエラー。Array#includes
には string
でなく Color
を渡さないといけないらしい。(playground)
渡すときに Color
とわかってたら Array#includes
で検証する必要ないですやん...。この問題については以下に書いてあった。
この記事では
function includes<T extends U, U>(coll: ReadonlyArray<T>, el: U): el is T {
return coll.includes(el as T);
}
みたいなヘルパ関数を解決方法として挙げていたけど、これだったら includes
にわたすときに as
でキャストすればいいんじゃないかという気もする。こんな感じで(playground)
function isColor(color: string): color is Color {
return colors.includes(color as Color);
}
もしくは string
にキャストした Set
( Array
でもいい)を作るとかでもいいかもしれない。(playground)
const colorsSet = new Set<string>(colors);
function isColor(color: string): color is Color {
return colorsSet.has(color);
}
他になにかいい方法あるんだろうか。
Discussion