【问题标题】:infer template string return unexpected infer result推断模板字符串返回意外的推断结果
【发布时间】:2021-03-04 15:19:54
【问题描述】:

我在想下面的Y 会返回test,但是,它返回never,有人知道原因和潜在的解决方案吗?

type X<T> = T extends `${infer U}.${number}` ? U : never
type Y = X<`test.${number}`>; // expect "test" but got never

Playground link

【问题讨论】:

  • 我认为这是设计限制。这里有 TS PR github.com/microsoft/TypeScript/pull/40336 找不到这样的功能
  • 它适用于 Xtest.5>
  • 感谢@captain-yossarian 是的,只是推断不适用于字符串部分。

标签: typescript


【解决方案1】:

取自template literals PR

占位符中的any、string、number、boolean或bigint类型中的任何一种都会导致模板文字解析为字符串类型。

此代码按预期工作:

type X<T> = T extends `test.${infer A}` ? A : never;

type Z = X<`test.${number}`>; // string
type Z1 = X<`test.${bigint}`>; // string
type Z2 = X<`test.${boolean}`>; // "false" | "true"

但是,此代码按您的预期工作:

type X<T> = T extends `${infer B}.${infer A}` ? B : never;

type Z = X<`test.${boolean}`>; // "test"

为什么它适用于boolean?因为 boolean 在底层是联合类型“true | false”。

所以,这应该可行:

type Z = X<`test.${1|2|3|4|5}`>; // test

如果我们已经知道它适用于联合类型,我认为我们可以生成一些数字联合。

取自我的bloganswer

type Values<T> = T[keyof T];

type LiteralDigits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;

type NumberString<T extends number> = `${T}`

type AppendDigit<T extends number | string> = `${T}${LiteralDigits}`


type MakeSet<T extends number> = {
  [P in T]: AppendDigit<P>;
};

type RemoveTrailingZero<
  T extends string
> = T extends `${infer Fst}${infer Rest}`
  ? Fst extends `0`
    ? RemoveTrailingZero<Rest>
    : `${Fst}${Rest}`
  : never;

type From_1_to_999 = RemoveTrailingZero<
  Values<
    {
      [P in Values<MakeSet<LiteralDigits>>]: AppendDigit<P>;
    }
  >
>;

type By<V extends NumberString<number>> = RemoveTrailingZero<
  Values<
    {
      [P in V]: AppendDigit<P>;
    }
  >
>;

/**
 * Did not use recursion here,
 * because my CPU will blow up
 */
type From_1_to_99999 =
  | From_1_to_999
  | By<From_1_to_999>
  | By<From_1_to_999 | By<From_1_to_999>>;

type X<T extends string> = T extends `${infer B}.${infer A}` ? B : never

type Z = X<`test.${From_1_to_99999}`>; // test

Playground

还有另一种解决方案,对 CPU 来说不是那么昂贵,但有自己的局限性:

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;

// Up to 42 - meaning of the life
type Result = Enumerate<43>; // 0 | 1 | 2 | ... | 42

type X<T extends string> = T extends `${infer B}.${infer A}` ? B : never

type Z = X<`test.${Result}`>; // test

Playground

所以由您决定哪种解决方案更适合您。

【讨论】:

    猜你喜欢
    • 2015-12-14
    • 2013-12-05
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-15
    相关资源
    最近更新 更多