我有几个命名空间,它们都包含一个同名的嵌套类,例如名为R
。我想做一个通用函数作为工厂,它可以:示例化一个R
的新示例,如果我只把命名空间作为参数传递的话,所以我得到了这里:
namespace A {
export class R {
public i = 0;
}
}
namespace B {
export class R {
public s = '';
}
}
type Input = typeof A | typeof B;
function Test<T extends Input>(v: T): InstanceType<T['R']> {
// Type 'A.R | B.R' is not assignable to type 'T["R"]'.
// 'T["R"]' could be instantiated with an arbitrary type which could be unrelated to 'A.R | B.R'.(2322)
return new v.R();
}
const instance = Test(A);
我的目的是让Test
函数自动推断并返回T['R']
。然而,我刚刚得到了上面显示的错误。
为什么v.R()
被推断为'A.R | B.R'
,而不是根据T缩小?
操场
1条答案
按热度按时间5n0oy7gb1#
TypeScript无法对依赖于generic类型参数的conditional types进行太多分析。一旦指定了类型参数,这些类型就可能变成任何类型,因此类型检查器大多数情况下就放弃了。它将这些类型视为 opaque,并且通常无法验证是否有任何特定的值可分配给它们。由于
InstanceType<>
实用程序类型是作为条件类型实现的,并且由于T
是泛型类型参数,因此类型InstanceType<T["R"]>
是这样一个不透明的类型,编译器将抱怨任何返回值尚未确切知道是InstanceType<T["R"]>
的return
语句。此外,当查看涉及
new v.R()
等泛型类型值的操作时,编译器通常会做出简化假设,即泛型类型参数可以扩展到其约束。所以在new v.R()
中,v.R
首先扩展为类型Input["R"]
,一个联合(参见microsoft/TypeScript#33181),然后new v.R()
变成联合类型A.R | B.R
,这与InstanceType<T["R"]>
不同,赋值失败。从本质上讲,编译器还不够聪明,无法遵循你的逻辑。
一种方法是通过重构来避免泛型条件类型。不要让
T
成为v
参数的类型,而是让它成为返回的预期示例类型:因为
v
的类型是{R: new()=>T}
,所以编译器知道v.R
的类型是new()=>T
。这是一个单一的构造签名,没有参数,并产生一个T
类型的值。因此new v.R()
已知是T
类型,并且该函数可以编译。由于您显然不希望传入具有可构造
R
属性的任意对象,而是明确地只允许A
和B
,因此我们我把
T
限制为InstanceType<Input["R"]>
。这计算为联合A.R | B.R
。因此,该函数将只允许具有R
属性的输入,该属性构造A.R
或B.R
的示例。当然,这并不重要,因为该函数可以很好地工作在任何在R
属性上具有零参数构造函数的对象上,所以您可能希望不约束T
,这取决于用例。好吧,让我们测试一下:
看起来不错。如果
T
受到约束,则以下内容将被拒绝:但是如果
T
是无约束的,那么它被接受并产生一个适当类型的示例:Playground链接到代码