TypeScript 从 const 模板文字推断模板文字

TypeScript infer temlate lieral from const template literal

我正在尝试使用精美的 TypeScript 模板文字来键入 REST API。我有一个适用于 const 字符串类型和 const Javascript 模板文字类型的系统,但不适用于具有未知值的 Javascript 模板文字。

考虑以下递归路径解析器。

type PathVariable = string;
type ExtractPathVariable<T extends string> = T extends `:${string}`
    ? PathVariable
    : T;
type PathParts<Path extends string> = Path extends `/${infer Rest}`
    ? PathParts<Rest>
    : Path extends `${infer Start}/${infer Rest}`
    ? [ExtractPathVariable<Start>, ...PathParts<`${Rest}`>]
    : Path extends `${infer Item}`
    ? [ExtractPathVariable<Item>]
    : never;

它给出了某些情况下的预期结果

// A == ["short", "url", "path"]
type A = PathParts<"/short/url/path">;
const b = `/short/url/path` as const;
// B == ["short", "url", "path"]
type B = PathParts<typeof b>;
// C == ["short", "${y}", "path"]
type C = PathParts<"/short/${y}/path">;
const d = `/short//path` as const;
// D == ["short", "45", "path"]
type D = PathParts<typeof d>;

然而,我最感兴趣的情况(因为我就是这样称呼我的API),它不起作用。

let y: unknown;
const e = `/short/${y}/path`;
// E == never
type E = PathParts<typeof e>;

有没有办法让 PathParts<typeof e> 工作?结果 E == ["short", string, "path"]E == ["short", unknown, "path"] 就可以了。

TypeScript 4.3 更新:

问题 microsoft/Typescript#43060 has been marked fixed by pull request microsoft/Typescript#43361, which should be released with TypeScript 4.3. At that point, your code above will work (as long as you use a const assertion 正如我在下面提到的那样):

let y: unknown;
const e = `/short/${y}/path` as const;
// const e: `/short/${string}/path`
type E = PathParts<typeof e>;
// type E = ["short", string, "path"]

您可以在此处查看实际效果:Playground link to code


TS 4.2 的先前答案:

抱歉,我认为从 TypeScript 4.2 开始这是不可能的。

首先,要接近这种行为,您需要使用 const assertion to tell the compiler that you'd like e to be inferred as a template literal type and not just string. While there is a consistency argument that, like const foo = "abc" is inferred as "abc", so const bar = `abc${x}` should be inferred as type `abc${string}` or the like, as requested in microsoft/TypeScript#41631, this change ended up breaking too much real world code。所以你需要 as const:

const e = `/short/${y}/path` as const
// const e: `/short/${string}/path`

但这就是我们所能做到的。您正在尝试将具有多个 infer 位置的模板文字类型与另一个具有“模式”文字的模板文字类型匹配 `${number}` (其中模式文字在 microsoft/TypeScript#40598. But according to microsoft/TypeScript#43060 中实现,这不是可能(它提到 `${number}` 而不是 `${string},但所有模式文字都会出现相同的情况):

type Simple<T> = T extends `${infer F}/${infer L}` ? [F, L] : never
type Works = Simple<`foo/bar`> // ["foo", "bar"];
type Broken = Simple<`foo/${string}`> // never
type AlsoBroken = Simple<`${string}/bar`> // never

该问题尚未归类为 bug/limitation/suggestion,但它与被视为建议的 microsoft/TypeScript#43243 相关。目前似乎还没有一种机制可以让这种推理发挥作用。我也没能找到任何行为合理的解决方法。

如果您希望看到这种情况发生,您可能想解决其中任何一个问题并给出 and/or 描述您的用例为何引人注目。

Playground link to code