从本质上讲,你在寻找一个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 any
,T extends unknown
,T 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