我正在寻找一种方法来推断一个更广泛的字符串类型键回来,可以分配给它.

type Foo = {
  [x: `/tea/${string}/cup`]: void;
  [x: `/coffee/${string}/time`]: void;
  [x: `/cake/${string}/tin`]: void;
}

type MatchesFooKey<T extends string> = T extends keyof Foo ? true : false;
type TestMatch = MatchesFooKey<'/coffee/123/time'>; // Gives true as it should

type InferFooKey<T extends string> = T extends (infer R extends keyof Foo) ? R : never;
type TestInfer = InferFooKey<'/coffee/123/time'>; // Gives '/coffee/123/time', would like to get `/coffee/${string}/time` instead

例如,给定'/coffee/123/time',我希望能够根据上面的 struct 推断出`coffee/${string}/time`.

现在,infer R并不产生它匹配的特定键,因为推断类型与我想要得到的方向是错误的.本质上,我想从Foo推断出一个特定的密钥并将其存储在R中.

有没有一种方法可以从特定的类型中推断出更广泛的类型?

Here's a Typescript playground link.

推荐答案

从本质上讲,你在寻找一个the Extract<T, U> utility type的版本,它提取出union T中的任何成员,这是U中的supertype,而不是U中的subtype. 这样,ExtractSubtype<keyof Foo, T>将是T可分配给它的keyof Foo的任何成员,而不是反过来.

The definition of Extract是:

type Extract<T, U> = T extends U ? T : never;

它是一个distributive conditional type,它将联合T分成其成员,并比较每个成员以查看它是否可分配给U.

我们可以这样写ExtractSubtype<T, U>:

type ExtractSubtype<T, U> =
  T extends any ? [U] extends [T] ? T : never : never;

我们还想分发超过T个,所以我们需要T extends ⋯ ? ⋯ : ⋯个. 但我们并不真的想要checkT,所以我们只想写一些永远是真的,比如T extends anyT extends unknownT extends T. 所以我们写T extends any ? ⋯ : never来分发超过T个.

我们真正要查的是[U] extends [T] ? T : never. 也就是说,U类型是否可分配给我们正在查看的T的成员.它被包装在[]中,变成了off分配性,因为这里我们不想把U分成unions 成员.

有了它,我们就能得到:

type InferFooKey<U extends string> = ExtractSubtype<keyof Foo, U>

type TestInfer = InferFooKey<'/coffee/123/time'>;
//   type TestInfer = `/coffee/${string}/time`^?

看起来不错.


当然,如果你不需要重复使用ExtractSubtype,我们可以try 尽可能多的内联操作:

type InferFooKey<U extends string, T = keyof Foo> =
  T extends any ? [U] extends [T] ? T : never : never;

type TestInfer = InferFooKey<'/coffee/123/time'>;
//  type TestInfer = `/coffee/${string}/time`^?

我在这里使用(滥用?)a generic type argument default使T总是等于keyof Foo,因此T extends any ? ⋯ : never分布在T上. 如果您刚刚try 了keyof Foo extends any ? ⋯ : never,它将不会分发(因为分发条件类型要求您判断泛型类型参数,而不是特定类型).

如果你希望在U个unions 之间分配,那么你可以这样写:

type InferFooKey<U extends string, T = keyof Foo> =
  T extends any ? U extends T ? T : never : never;

这取决于当U是unions 时,你希望InferFooKey<U>做什么.

type Test2 = InferFooKey<'/tea/abc/cup' | '/coffee/def/time'>;
// never? since no key is assignable to that union... or
// `/tea/${string}/cup` | `/coffee/${string}/time`

但无论哪种方式都适用于你所说的例子.

Playground link to code

Typescript相关问答推荐

React-Native运行方法通过监听另一个状态来更新一个状态

类型脚本强制泛型类型安全

接口的额外属性作为同一接口的方法的参数类型'

在NextJS中获得Spotify Oauth,但不工作'

类型{}上不存在属性&STORE&A;-MobX&A;REACT&A;TYPE脚本

如何在typescript中设置对象分配时的键类型和值类型

TypeScrip界面属性依赖于以前的属性-针对多种可能情况的简明解决方案

TypeScrip泛型类未提供预期的类型错误

Angular 自定义验证控件未获得表单值

使用TypeScrip的类型继承

APP_INITIALIZER在继续到其他Provider 之前未解析promise

根据类型脚本中的泛型属性 Select 类型

我在Angular 17中的environment.ts文件中得到了一个属性端点错误

RXJS将对键盘/鼠标点击组合做出react

在类型脚本中创建显式不安全的私有类成员访问函数

TypeScript:如何使用泛型和子类型创建泛型默认函数?

我可以在 Typescript 中重用函数声明吗?

Typescript 确保通用对象不包含属性

React 中如何比较两个对象数组并获取不同的值?

通用可选函数参数返回类型