在这种情况下如何推断正确的泛型类型?



考虑以下代码:

type secondaryObjectConstraint = {
[key: string]: number
}
abstract class Base<TObject extends object, TSecondaryObject extends secondaryObjectConstraint> {}

type secondaryObjectType = {
myProp: number
}
class ExtendedObject extends Base<{}, secondaryObjectType> {}
type inferSecondaryObject<M extends Base<any, any>> = M extends Base<any, infer TSecondaryObject> ? TSecondaryObject : never;
const a: inferSecondaryObject<ExtendedObject> = {};

在上面的例子中,a得到的是secondaryObjectConstraint的类型,而不是更精确的secondaryObjectType。我哪里做错了?我如何在上面的例子中推断出secondaryObjectType呢?

游乐场链接

请参阅本常见问题解答。

TypeScript的类型系统很大程度上是结构化的,而不是标称的。如果两个类型AB具有相同的结构(例如,它们都具有相同属性类型的相同属性名),则TypeScript认为它们是相同的类型。编译器可以自由地将AB完全互换处理。即使AB具有不同的名称声明位置,也是如此。

让我们看看你的Base类:

abstract class Base<
TObject extends object, 
TSecondaryObject extends SecondaryObjectConstraint
> { }

这个类的结构是…空的。Base<X, Y>的实例根本没有已知的属性。因此,通过结构类型,它与空对象类型{}相同。它对TObjectTSecondaryObject类型参数没有结构依赖。

这也意味着Base<{}, SecondaryObjectType>Base<object, SecondaryObjectConstraint>在结构上彼此相同,编译器可以自由地将它们互换处理。因此,当试图从类型Base<X, Y>推断XY时,所有的赌注都是无效的。您所知道的是,您将得到一些与其约束一致的类型。

编译器将能够从Base<X, Y> extends Base<X, infer T> ? T : never返回Y可能的,但不能保证。不幸的是,使用中间ExtendedObject类,您最终得到的是约束类型,而不是您希望的特定类型。


如果你想要更多的保证,你应该确保你的类型对你关心的类型有结构依赖。泛型类型应该始终以某种结构方式使用它们的类型参数。例如:

abstract class Base<T extends object, U extends SecondaryObjectConstraint> {
t!: T;
u!: U
}

因此,Base<T, U>t属性类型为T,u属性类型为U。现在你得到了想要的行为:

type IOEO = InferSecondaryObject<ExtendedObject>;
/* type IOEO = {
myProp: number;
} */

Playground链接到代码

相关内容

最新更新