从模板文字类型中提取值

Extract values from template literal types

假设我有这样的类型:

type SegmentBase = string;
type ParamSegment = `:${string}`;
type Segment = SegmentBase | ParamSegment;

type Path = `${Segment}/${Segment}`;

现在是否可以构造一个类型 Extractor<T extends Path> 以下列方式提取 ParamSegment${string} 部分:

Extractor<'foo/:bar'>
// turns into
{
    bar: string;
}
Extractor<':foo/:bar'>
// turns into
{
    foo: string;
    bar: string;
}
Extractor<'foo/bar'>
// turns into
{}

我想你需要这样的东西:

type Extractor<S extends string> = 
  S extends `${infer L}/${infer R}`
    ? {
      [K in L | R as K extends `:${infer Key}` ? Key : never]: string
    }
    : never

部分测试用例:

type T1 = Extractor<'foo/:bar'>
// type T1 = {
//     bar: string;
// }

type T2 = Extractor<':foo/:bar'>
// type T2 = {
//     foo: string;
//     bar: string;
// }

type T3 = Extractor<'foo/bar'>
// type T3 = {}

如果这不适合您的用例,请添加更多测试用例。

Playground

您需要为此创建一个递归辅助类型。

首先,创建一个匹配参数名称的类型

type _UnwrapParam<P extends string, S extends string[]> = P extends `:${infer Q}` ? [Q, ...S] : S;

type _Match<T extends string, S extends string[]> = T extends `${infer P}/${infer R}`
  ? [...UnwrapParam<P, S>, ..._Match<R, S>]
  : _UnwrapParam<T, S>

export type Match<T extends string> = _Match<T, []>[number];

那你可以做

export type Extractor<T extends string> = {
  [K in Match<T>]: string;
};

编辑: 从 Typescript 4.5 开始,最好按如下方式实现 _Match 类型,这样它可以受益于 Tail recursion elimination.

type _Match<
  T extends string,
  S extends string[]
> = T extends `${infer P}/${infer R}`
  ? _Match<R, _UnwrapParam<P, S>>
  : _UnwrapParam<T, S>;