TypeScript 模板文字类型 - 如何推断数字类型?
TypeScript Template Literal Type - how to infer numeric type?
// from a library
type T = null | "auto" | "text0" | "text1" | "text2" | "text3" | "text4";
//in my code
type N = Extract<T, `text${number}`> extends `text${infer R}` ? R : never
上面的代码 N
等价于 "0" | "1" | "2" | "3" | "4"
。如何将其转换为数字类型,即 0 | 1 | 2 | 3 | 4
?
已经尝试将 & number
放在某些地方,例如 infer R & number
,但是 none 有效。
更新
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number] // 0.. 998
type ConvertToNumber<T extends string, Range extends number> =
(Range extends any
? (`${Range}` extends T
? Range
: never)
: never)
type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125
P.S。不好意思命名,我不擅长。
目前看来这是不可能的,但有一个解决方法。
您可以为 0..42
:
范围内的数字创建 Dictionary
// from a library
type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never
type T = null | "auto" | Texts<Enumerate<43>>;
type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;
type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];
type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;
type Dictionary = {
[Prop in Enumerate<43> as `${Prop}`]: Prop
}
// 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42
type N =
Extract<T, `text${number}`> extends `text${infer R}`
? R extends keyof Dictionary
? Dictionary[R]
: never
: never
合并Tail recursion PR后可能会产生更长的距离
更新 - 就像我承诺的那样
尝试
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
// 0 , 1, 2 ... 998
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never
type T = null | "auto" | Texts<NumberRange>;
type Dictionary = {
[Prop in NumberRange as `${Prop}`]: Prop
}
// 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 998
type N =
Extract<T, `text${number}`> extends `text${infer R}`
? R extends keyof Dictionary
? Dictionary[R]
: never
: never
您可以在 TS playground 中使用 TS 版本 4.5(每晚)尝试上述解决方案
代码就简单多了。
这里有 javascript 代表 Mapped
:
const Mapped = (N: number, Result: number[] = []): number[] => {
if (Result.length === N) {
return Result
}
return Mapped(N, [...Result, Result.length])
}
没什么复杂的。尾递归。
感谢@captain-yossarian 的回答。我发现您还可以使用字符串索引保留 Mapped<MAXIMUM_ALLOWED_BOUNDARY>
和索引的元组。当然,Range
必须是一个元组。
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>; // <- tuple [0, 1, 2, 3, ...]
type ConvertToNumber<T extends string, Range extends number[]> =
T extends keyof Range ? Range[T] : never;
type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125
// from a library
type T = null | "auto" | "text0" | "text1" | "text2" | "text3" | "text4";
//in my code
type N = Extract<T, `text${number}`> extends `text${infer R}` ? R : never
上面的代码 N
等价于 "0" | "1" | "2" | "3" | "4"
。如何将其转换为数字类型,即 0 | 1 | 2 | 3 | 4
?
已经尝试将 & number
放在某些地方,例如 infer R & number
,但是 none 有效。
更新
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number] // 0.. 998
type ConvertToNumber<T extends string, Range extends number> =
(Range extends any
? (`${Range}` extends T
? Range
: never)
: never)
type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125
P.S。不好意思命名,我不擅长。
目前看来这是不可能的,但有一个解决方法。
您可以为 0..42
:
Dictionary
// from a library
type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never
type T = null | "auto" | Texts<Enumerate<43>>;
type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;
type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];
type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;
type Dictionary = {
[Prop in Enumerate<43> as `${Prop}`]: Prop
}
// 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42
type N =
Extract<T, `text${number}`> extends `text${infer R}`
? R extends keyof Dictionary
? Dictionary[R]
: never
: never
合并Tail recursion PR后可能会产生更长的距离
更新 - 就像我承诺的那样
尝试
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
// 0 , 1, 2 ... 998
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never
type T = null | "auto" | Texts<NumberRange>;
type Dictionary = {
[Prop in NumberRange as `${Prop}`]: Prop
}
// 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 998
type N =
Extract<T, `text${number}`> extends `text${infer R}`
? R extends keyof Dictionary
? Dictionary[R]
: never
: never
您可以在 TS playground 中使用 TS 版本 4.5(每晚)尝试上述解决方案 代码就简单多了。
这里有 javascript 代表 Mapped
:
const Mapped = (N: number, Result: number[] = []): number[] => {
if (Result.length === N) {
return Result
}
return Mapped(N, [...Result, Result.length])
}
没什么复杂的。尾递归。
感谢@captain-yossarian 的回答。我发现您还可以使用字符串索引保留 Mapped<MAXIMUM_ALLOWED_BOUNDARY>
和索引的元组。当然,Range
必须是一个元组。
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>; // <- tuple [0, 1, 2, 3, ...]
type ConvertToNumber<T extends string, Range extends number[]> =
T extends keyof Range ? Range[T] : never;
type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125