TypeScript 模板文字类型 - 如何推断数字类型?

bur*_*tek 19 typescript typescript-types

// 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
Run Code Online (Sandbox Code Playgroud)

TS游乐场

对于上面的代码段N将相当于"0" | "1" | "2" | "3" | "4". 我怎样才能将其转换为数字类型,即0 | 1 | 2 | 3 | 4?已经尝试 & number在某些地方放置,例如infer R & number,但都不起作用。

cap*_*ian 38

2022 年 6 月 6 日更新 TS 4.8

从 TypeScript 4.8 开始,无需数字联合 hack 就可以实现。参见公关


//in my code
type ParseInt<T extends `text${number}`> =
  T extends any
  ? (T extends `text${infer Digit extends number}`
    ? Digit
    : never)
  : never

// 0 | 1 | 2 | 3 | 4
type Result = ParseInt<"text0" | "text1" | "text2" | "text3" | "text4">
Run Code Online (Sandbox Code Playgroud)

操场

更新

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
Run Code Online (Sandbox Code Playgroud)

操场

PS抱歉命名,我不擅长。

目前看来这是不可能的,但有一个解决方法。

您可以创建Dictionary范围内的数字0..42

// 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
Run Code Online (Sandbox Code Playgroud)

合并尾递归 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
Run Code Online (Sandbox Code Playgroud)

操场

您可以在 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])
}
Run Code Online (Sandbox Code Playgroud)

没什么复杂的。尾递归。


  • 我需要一点时间来了解这里发生了什么,但这对于我所需要的可能有点过分了。不过还是谢谢你的这个想法 (2认同)