考虑以下代码:
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的类型系统很大程度上是结构化的,而不是标称的。如果两个类型A
和B
具有相同的结构(例如,它们都具有相同属性类型的相同属性名),则TypeScript认为它们是相同的类型。编译器可以自由地将A
和B
完全互换处理。即使A
和B
具有不同的名称或声明位置,也是如此。
让我们看看你的Base
类:
abstract class Base<
TObject extends object,
TSecondaryObject extends SecondaryObjectConstraint
> { }
这个类的结构是…空的。Base<X, Y>
的实例根本没有已知的属性。因此,通过结构类型,它与空对象类型{}
相同。它对TObject
或TSecondaryObject
类型参数没有结构依赖。
这也意味着Base<{}, SecondaryObjectType>
和Base<object, SecondaryObjectConstraint>
在结构上彼此相同,编译器可以自由地将它们互换处理。因此,当试图从类型Base<X, Y>
推断X
和Y
时,所有的赌注都是无效的。您所知道的是,您将得到一些与其约束一致的类型。
编译器将能够从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链接到代码