如何在 Typescript 中创建 UUID 模板文字类型?
How to create a UUID template literal type in Typescript?
有没有人使用新的模板文字类型在 Typescript 中成功编写 UUID 类型?
例如:
const id:UUID = "f172b0f1-ea0a-4116-a12c-fc339cb451b6"
这家伙试过:UUID Tweet
但类型太复杂:“表达式产生的联合类型太复杂而无法表示。(2590)”:Example
他的类型:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Alphanumeric = Alphabetic | Numeric
type Repeat<
Char extends string,
Count extends number,
Joined extends string = ``,
Acc extends 0[] = []
> = Acc['length'] extends Count ? Joined : Repeat<Char, Count, `${Joined}${Char}`, [0,...Acc]>
type UUIDV4 = `${Repeat<Alphanumeric, 8>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 12>}````
我不确定是否可行,因为 TS 有他自己的限制。
考虑这个例子:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Numeric = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0
type StringNumber<T extends number> = `${T}`
type Alphanumeric = Alphabetic | StringNumber<Numeric>
type Result<T extends string> = T extends any ? T : never;
interface Unit {
value: Alphanumeric
}
type Check = `${Alphanumeric}${Alphanumeric}${Alphanumeric}`
Check
生成超过 46.6K 种可能状态。
如果您再添加一个 ${Alphanumeric}
它将达到限制。
我怀疑是否可以在打字稿类型系统中表示。
也许在Tail recursive evaluation of conditional types
之后有可能
我认为可以只验证字符串是否有效,就像我对十六进制表示所做的 here 但它似乎也不起作用。
您仍然可以验证已知的 uuid 文字:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Numeric = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0
type StringNumber<T extends number> = `${T}`
type Alphanumeric = Alphabetic | StringNumber<Numeric>
type Separator = '-'
type UUID = 'f172b0f1-ea0a-4116-a12c-fc339cb451b6'
type Template = [8, 4, 4, 4, 12];
type IsValid<T, Cache extends string[] = []> =
T extends string ?
T extends ''
? Cache
: T extends `${infer Char}${infer Rest}`
? Char extends Alphanumeric ? IsValid<Rest, [...Cache, Char]>
: never
: never
: never
type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type StringLength<T, Cache extends any[] = []> =
T extends string ?
T extends ''
? Cache['length']
: T extends `${infer Char}${infer Rest}`
? StringLength<Rest, [...Cache, Char]>
: never
: never
type Every<T extends any[], Cache extends any[] = []> =
T extends []
? Cache
: T extends [infer Head, ...infer Rest]
? Every<Rest, [...Cache, IsValid<Head>['length']]>
: never
type IsCorrect<T extends string,> = Every<Split<T, Separator>> extends Template ? true : false
type Test = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b6'> // ok
type Test2 = IsCorrect<'f172b0f1-ea0a-4116-a12c'> // false
type Test3 = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b6--'> // false
type Test4 = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b'> // false
type Test5 = IsCorrect<'f172b0f1-ea0a-4116-a%%c-fc339cb451b'> // false
type Test6 = IsCorrect<'f172b0f1ea0a-4116-a12c-fc339cb451b6'> // false
Yuri Bogolomov 在他的博客上发布了一个解决方案:https://ybogomolov.me/type-level-uuid/。
这是他的解决方案(参见 TypeScript Playground):
type VersionChar =
| '1' | '2' | '3' | '4' | '5';
type Char =
| '0' | '1' | '2' | '3'
| '4' | '5' | '6' | '7'
| '8' | '9' | 'a' | 'b'
| 'c' | 'd' | 'e' | 'f';
type Prev<X extends number> =
[never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...never[]][X];
type HasLength<S extends string, Len extends number> = [Len] extends [0]
? (S extends '' ? true : never)
: (S extends `${infer C}${infer Rest}`
? (Lowercase<C> extends Char ? HasLength<Rest, Prev<Len>> : never)
: never);
type Char4<S extends string> = true extends HasLength<S, 4> ? S : never;
type Char8<S extends string> = true extends HasLength<S, 8> ? S : never;
type Char12<S extends string> = true extends HasLength<S, 12> ? S : never;
type VersionGroup<S extends string> = S extends `${infer Version}${infer Rest}`
? (Version extends VersionChar
? (true extends HasLength<Rest, 3> ? S : never)
: never)
: never;
type NilUUID = '00000000-0000-0000-0000-000000000000';
type UUID<S extends string> = S extends NilUUID
? S
: (S extends `${infer S8}-${infer S4_1}-${infer S4_2}-${infer S4_3}-${infer S12}`
? (S8 extends Char8<S8>
? (S4_1 extends Char4<S4_1>
? (S4_2 extends VersionGroup<S4_2>
? (S4_3 extends Char4<S4_3>
? (S12 extends Char12<S12>
? S
: never)
: never)
: never)
: never)
: never)
: never);
const getUser = <S extends string>(id: UUID<S>): void => console.log(id);
getUser('00000000-0000-0000-0000-000000000000'); // ✅ special Nil UUID
getUser('11111111-1111-0111-1111-111111111111'); // ✅ error: version 0 is a special case
getUser('11111111-1111-1111-1111-111111111111'); // ✅ version 1
getUser('11111111-1111-2111-1111-111111111111'); // ✅ version 2
getUser('11111111-1111-3111-1111-111111111111'); // ✅ version 3
getUser('11111111-1111-4111-1111-111111111111'); // ✅ version 4
getUser('11111111-1111-5111-1111-111111111111'); // ✅ version 5
getUser('11111111-1111-6111-1111-111111111111'); // ✅ error: version 6 doesn't exist
getUser('11111111-1111-1111-1111-11111111111'); // ✅ error: invalid format
有没有人使用新的模板文字类型在 Typescript 中成功编写 UUID 类型?
例如:
const id:UUID = "f172b0f1-ea0a-4116-a12c-fc339cb451b6"
这家伙试过:UUID Tweet
但类型太复杂:“表达式产生的联合类型太复杂而无法表示。(2590)”:Example
他的类型:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Alphanumeric = Alphabetic | Numeric
type Repeat<
Char extends string,
Count extends number,
Joined extends string = ``,
Acc extends 0[] = []
> = Acc['length'] extends Count ? Joined : Repeat<Char, Count, `${Joined}${Char}`, [0,...Acc]>
type UUIDV4 = `${Repeat<Alphanumeric, 8>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 4>}-${Repeat<Alphanumeric, 12>}````
我不确定是否可行,因为 TS 有他自己的限制。 考虑这个例子:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Numeric = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0
type StringNumber<T extends number> = `${T}`
type Alphanumeric = Alphabetic | StringNumber<Numeric>
type Result<T extends string> = T extends any ? T : never;
interface Unit {
value: Alphanumeric
}
type Check = `${Alphanumeric}${Alphanumeric}${Alphanumeric}`
Check
生成超过 46.6K 种可能状态。
如果您再添加一个 ${Alphanumeric}
它将达到限制。
我怀疑是否可以在打字稿类型系统中表示。
也许在Tail recursive evaluation of conditional types
之后有可能我认为可以只验证字符串是否有效,就像我对十六进制表示所做的 here 但它似乎也不起作用。
您仍然可以验证已知的 uuid 文字:
type Alphabetic = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
type Numeric = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0
type StringNumber<T extends number> = `${T}`
type Alphanumeric = Alphabetic | StringNumber<Numeric>
type Separator = '-'
type UUID = 'f172b0f1-ea0a-4116-a12c-fc339cb451b6'
type Template = [8, 4, 4, 4, 12];
type IsValid<T, Cache extends string[] = []> =
T extends string ?
T extends ''
? Cache
: T extends `${infer Char}${infer Rest}`
? Char extends Alphanumeric ? IsValid<Rest, [...Cache, Char]>
: never
: never
: never
type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type StringLength<T, Cache extends any[] = []> =
T extends string ?
T extends ''
? Cache['length']
: T extends `${infer Char}${infer Rest}`
? StringLength<Rest, [...Cache, Char]>
: never
: never
type Every<T extends any[], Cache extends any[] = []> =
T extends []
? Cache
: T extends [infer Head, ...infer Rest]
? Every<Rest, [...Cache, IsValid<Head>['length']]>
: never
type IsCorrect<T extends string,> = Every<Split<T, Separator>> extends Template ? true : false
type Test = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b6'> // ok
type Test2 = IsCorrect<'f172b0f1-ea0a-4116-a12c'> // false
type Test3 = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b6--'> // false
type Test4 = IsCorrect<'f172b0f1-ea0a-4116-a12c-fc339cb451b'> // false
type Test5 = IsCorrect<'f172b0f1-ea0a-4116-a%%c-fc339cb451b'> // false
type Test6 = IsCorrect<'f172b0f1ea0a-4116-a12c-fc339cb451b6'> // false
Yuri Bogolomov 在他的博客上发布了一个解决方案:https://ybogomolov.me/type-level-uuid/。
这是他的解决方案(参见 TypeScript Playground):
type VersionChar =
| '1' | '2' | '3' | '4' | '5';
type Char =
| '0' | '1' | '2' | '3'
| '4' | '5' | '6' | '7'
| '8' | '9' | 'a' | 'b'
| 'c' | 'd' | 'e' | 'f';
type Prev<X extends number> =
[never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...never[]][X];
type HasLength<S extends string, Len extends number> = [Len] extends [0]
? (S extends '' ? true : never)
: (S extends `${infer C}${infer Rest}`
? (Lowercase<C> extends Char ? HasLength<Rest, Prev<Len>> : never)
: never);
type Char4<S extends string> = true extends HasLength<S, 4> ? S : never;
type Char8<S extends string> = true extends HasLength<S, 8> ? S : never;
type Char12<S extends string> = true extends HasLength<S, 12> ? S : never;
type VersionGroup<S extends string> = S extends `${infer Version}${infer Rest}`
? (Version extends VersionChar
? (true extends HasLength<Rest, 3> ? S : never)
: never)
: never;
type NilUUID = '00000000-0000-0000-0000-000000000000';
type UUID<S extends string> = S extends NilUUID
? S
: (S extends `${infer S8}-${infer S4_1}-${infer S4_2}-${infer S4_3}-${infer S12}`
? (S8 extends Char8<S8>
? (S4_1 extends Char4<S4_1>
? (S4_2 extends VersionGroup<S4_2>
? (S4_3 extends Char4<S4_3>
? (S12 extends Char12<S12>
? S
: never)
: never)
: never)
: never)
: never)
: never);
const getUser = <S extends string>(id: UUID<S>): void => console.log(id);
getUser('00000000-0000-0000-0000-000000000000'); // ✅ special Nil UUID
getUser('11111111-1111-0111-1111-111111111111'); // ✅ error: version 0 is a special case
getUser('11111111-1111-1111-1111-111111111111'); // ✅ version 1
getUser('11111111-1111-2111-1111-111111111111'); // ✅ version 2
getUser('11111111-1111-3111-1111-111111111111'); // ✅ version 3
getUser('11111111-1111-4111-1111-111111111111'); // ✅ version 4
getUser('11111111-1111-5111-1111-111111111111'); // ✅ version 5
getUser('11111111-1111-6111-1111-111111111111'); // ✅ error: version 6 doesn't exist
getUser('11111111-1111-1111-1111-11111111111'); // ✅ error: invalid format