在多个级别上推断 TypeScript 中的泛型类型参数
Infer generic type parameters in TypeScript over multiple levels
我的 TypeScript 代码中有以下常见场景:
interface TopLevelInterface<A extends BottomLevelInterface<B>, B>
和 BottomLevelInterface
的实现如下所示:
class BottomLevelClass implements BottomLevelInterface<MyType>
我的问题是:在实现 TopLevelInterface
时,我不仅需要传递 A
的类型参数,即 BottomLevelClass
,还需要传递 [=19] 的第二个类型参数=] 在上例中为 MyType
。
为什么我需要指定 B
这可以通过查看 BottomLevelClass
的类型参数轻松推断出来?
例如,在实现TopLevelInterface
时,我需要指定以下内容:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>
而不是应该足够的较短版本:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>
为什么这是必要的?如何通过查看第一个参数来推断第二个类型参数?我想出的唯一解决方案是在 TypeScript 2.8+ 中使用 infer
和默认分配。此解决方案如下所示:
interface TopLevelInterface<A extends BottomLevelInterface<B>,
B = A extends BottomLevelInterface<infer _B> ? _B : any>
但是,我无法将其正确应用于三层 class 层次结构。在下面的 StackBlitz 中,您可以看到我无法获得 TopLevelClass
的 属性 model
的正确类型约束。它应该被推断为 SomeType
类型,但被推断为 never
。在 MiddleLevelClass
它确实可以正常工作。
https://stackblitz.com/edit/typescript-pcxnzo?file=infer-generics.ts
任何人都可以解释这个问题或实现预期结果的更好方法吗?
MiddleLevelClass
中的蓄意错误正在影响 TopLevelClass
的行为,因此对于有效的测试,我们应该 TopLevelClass
基于正确的 MiddleLevelClass
并使用单独 BadMiddleLevelClass
以证明那里的错误。
您的第一个问题是您的条件类型有一个 "else" 大小写 any
,这将倾向于隐藏错误。 never
往往更好并且被广泛使用,尽管完整的解决方案需要 a unique invalid
type.
有了这些更改,主要问题似乎是当您编写 TopLevelInterface<MiddleLevelClass>
并且 TypeScript 尝试计算 B = M extends MiddleLevelInterface<infer _B> ? _B : never
时,_B
没有推论,因为 B
参数实际上并未被 MiddleLevelInterface
或 MiddleLevelClass
使用。参见 this FAQ。添加一个使用 B
的虚拟可选 属性 可以解决问题。 (我猜你在实际应用程序中使用了 B
,否则你不会声明 B
,但你在简化示例中删除了使用?)
新代码:
export class SomeType {
x: string;
}
export interface BottomLevelInterface<T> {
model : T;
}
export class BottomLevelClass implements BottomLevelInterface<SomeType> {
model: SomeType;
}
export interface MiddleLevelInterface<B extends BottomLevelInterface<T>,
T = B extends BottomLevelInterface<infer _T> ? _T : never> {
_dummy_B?: B;
model: T;
}
export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
_dummy_B?: BottomLevelClass;
model: SomeType;
}
export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
// here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
model: string;
}
export interface TopLevelInterface <M extends MiddleLevelInterface<B, T>,
B extends BottomLevelInterface<T> = M extends MiddleLevelInterface<infer _B> ? _B : never,
T = B extends BottomLevelInterface<infer _T> ? _T : never> {
model: T;
}
export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
// now there is an error here
model: string;
}
基于 jcalz 的建议(感谢!)的原始问题的替代解决方案:不使用多个类型参数,而是使用辅助类型别名从 B
类型确定 T
类型和每次需要时,从 M
键入 B
。这是代码:
export class SomeType {
x: string;
}
export interface BottomLevelInterface<T> {
model : T;
}
export class BottomLevelClass implements BottomLevelInterface<SomeType> {
model: SomeType;
}
type TfromB<B extends BottomLevelInterface<any>> = B extends BottomLevelInterface<infer T> ? T : never;
export interface MiddleLevelInterface<B extends BottomLevelInterface<any>> {
_dummy_B?: B;
model: TfromB<B>;
}
export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
_dummy_B?: BottomLevelClass;
model: SomeType;
}
export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
// here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
model: string;
}
type BfromM<M extends MiddleLevelInterface<any>> = M extends MiddleLevelInterface<infer B> ? B : never;
export interface TopLevelInterface <M extends MiddleLevelInterface<any>> {
model: TfromB<BfromM<M>>;
}
export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
// now there is an error here
model: string;
}
如果对条件类型而不是显式类型参数的依赖使超出这个简单示例的某些操作变得更加困难,我不会感到惊讶。你可以试试看。
我的 TypeScript 代码中有以下常见场景:
interface TopLevelInterface<A extends BottomLevelInterface<B>, B>
和 BottomLevelInterface
的实现如下所示:
class BottomLevelClass implements BottomLevelInterface<MyType>
我的问题是:在实现 TopLevelInterface
时,我不仅需要传递 A
的类型参数,即 BottomLevelClass
,还需要传递 [=19] 的第二个类型参数=] 在上例中为 MyType
。
为什么我需要指定 B
这可以通过查看 BottomLevelClass
的类型参数轻松推断出来?
例如,在实现TopLevelInterface
时,我需要指定以下内容:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>
而不是应该足够的较短版本:
class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>
为什么这是必要的?如何通过查看第一个参数来推断第二个类型参数?我想出的唯一解决方案是在 TypeScript 2.8+ 中使用 infer
和默认分配。此解决方案如下所示:
interface TopLevelInterface<A extends BottomLevelInterface<B>,
B = A extends BottomLevelInterface<infer _B> ? _B : any>
但是,我无法将其正确应用于三层 class 层次结构。在下面的 StackBlitz 中,您可以看到我无法获得 TopLevelClass
的 属性 model
的正确类型约束。它应该被推断为 SomeType
类型,但被推断为 never
。在 MiddleLevelClass
它确实可以正常工作。
https://stackblitz.com/edit/typescript-pcxnzo?file=infer-generics.ts
任何人都可以解释这个问题或实现预期结果的更好方法吗?
MiddleLevelClass
中的蓄意错误正在影响 TopLevelClass
的行为,因此对于有效的测试,我们应该 TopLevelClass
基于正确的 MiddleLevelClass
并使用单独 BadMiddleLevelClass
以证明那里的错误。
您的第一个问题是您的条件类型有一个 "else" 大小写 any
,这将倾向于隐藏错误。 never
往往更好并且被广泛使用,尽管完整的解决方案需要 a unique invalid
type.
有了这些更改,主要问题似乎是当您编写 TopLevelInterface<MiddleLevelClass>
并且 TypeScript 尝试计算 B = M extends MiddleLevelInterface<infer _B> ? _B : never
时,_B
没有推论,因为 B
参数实际上并未被 MiddleLevelInterface
或 MiddleLevelClass
使用。参见 this FAQ。添加一个使用 B
的虚拟可选 属性 可以解决问题。 (我猜你在实际应用程序中使用了 B
,否则你不会声明 B
,但你在简化示例中删除了使用?)
新代码:
export class SomeType {
x: string;
}
export interface BottomLevelInterface<T> {
model : T;
}
export class BottomLevelClass implements BottomLevelInterface<SomeType> {
model: SomeType;
}
export interface MiddleLevelInterface<B extends BottomLevelInterface<T>,
T = B extends BottomLevelInterface<infer _T> ? _T : never> {
_dummy_B?: B;
model: T;
}
export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
_dummy_B?: BottomLevelClass;
model: SomeType;
}
export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
// here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
model: string;
}
export interface TopLevelInterface <M extends MiddleLevelInterface<B, T>,
B extends BottomLevelInterface<T> = M extends MiddleLevelInterface<infer _B> ? _B : never,
T = B extends BottomLevelInterface<infer _T> ? _T : never> {
model: T;
}
export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
// now there is an error here
model: string;
}
基于 jcalz 的建议(感谢!)的原始问题的替代解决方案:不使用多个类型参数,而是使用辅助类型别名从 B
类型确定 T
类型和每次需要时,从 M
键入 B
。这是代码:
export class SomeType {
x: string;
}
export interface BottomLevelInterface<T> {
model : T;
}
export class BottomLevelClass implements BottomLevelInterface<SomeType> {
model: SomeType;
}
type TfromB<B extends BottomLevelInterface<any>> = B extends BottomLevelInterface<infer T> ? T : never;
export interface MiddleLevelInterface<B extends BottomLevelInterface<any>> {
_dummy_B?: B;
model: TfromB<B>;
}
export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
_dummy_B?: BottomLevelClass;
model: SomeType;
}
export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
// here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
model: string;
}
type BfromM<M extends MiddleLevelInterface<any>> = M extends MiddleLevelInterface<infer B> ? B : never;
export interface TopLevelInterface <M extends MiddleLevelInterface<any>> {
model: TfromB<BfromM<M>>;
}
export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
// now there is an error here
model: string;
}
如果对条件类型而不是显式类型参数的依赖使超出这个简单示例的某些操作变得更加困难,我不会感到惊讶。你可以试试看。