主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ
書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。
2022/11/14
TypeScript には、 Template Literal Types というものがあり、例えば、以下のような型を表現することが出来ます。
type Pixel = `${number}px`;
const a: Pixel = "14px"; // valid
const b: Pixel = "20pt"; // invalid
これで、色を表現してみましょう。
といっても、こんな感じで出来ます。
type HexDigit =
| "0"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "a"
| "b"
| "c"
| "d"
| "e"
| "f"
| "A"
| "B"
| "C"
| "D"
| "E"
| "F";
type ColorHex<T extends string> =
T extends `#${HexDigit}${HexDigit}${HexDigit}${infer Rest}`
? Rest extends ``
? T
: Rest extends `${HexDigit}${HexDigit}${HexDigit}`
? T
: never
: never;
type ColorRGB<T extends string> = T extends `rgb(${number},${number},${number})`
? T
: never;
type ColorRGBA<T extends string> =
T extends `rgba(${number},${number},${number},${number})` ? T : never;
type Color = string & { __type: "Color" };
const color = <T extends string>(
w: ColorHex<T> | ColorRGB<T> | ColorRGBA<T>
): Color => {
return w as string as Color;
};
// valid
const c0: Color = color("rgb(1, 1, 1)");
const c1: Color = color("rgba(1, 1, 1, 1)");
const c2: Color = color("#12345a");
// invalid
const c3: Color = "#12345a";
const c4: Color = color("#fffa");
const c5: Color = color("#zzz");
ColorHex<T>
型は、単純に #xxx
もしくは #xxxxxx
をパースします。
ただし、 type ColorHex<T extends string> = T extends `#${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}` : never;
とは出来ません。
というのも、そのようにした場合、 TypeScript コンパイラの制限に引っかかって、型エラーが発生します。
ちなみにエラー内容は Expression produces a union type that is too complex to represent.(2590)
で、多すぎるぞ!って感じですね。
なので、第 4 引数(?)以降は infer
で受け取って、再度 infer
した部分が要件を満たすかどうかをチェックすれば良いです。
残りの 2 つ、ColorRGB
と ColorRGBA
は、単純な Template Literal Types なので、特に解説は必要ないはずです。
あとは、これらを color
関数の引数として扱い、受け取りたい側が Color
を型指定してあげれば、型レベルでバリデーションが行えます。
ということで、メモでした。