[英]Typescript compile error when assigning class type to a generic variable
我试图将类型设置为typescript中的泛型变量。 它使用通用版本时显示编译错误。
export class A<T extends Al> { }
export class B extends A<Bl> { }
export class Al { }
export class Bl extends Al { }
show<T extends A<Al>>() {
let t: Type<T>; // "t = B" shows compile error but seem to work
// let t: Type<A<Al>>; // "t = B" shows no compile error and still works
t = B;
}
我还在这里创建了一个stackblitz示例,以便您可以看到它是如何工作的。
我希望如果B extends A<Al>
并且T extends A<Al>
,则通用版本let t: Type<T>; t = B;
let t: Type<T>; t = B;
上班。 相反,我必须使用let t: Type<A<Al>>; t = B;
let t: Type<A<Al>>; t = B;
我想知道我是否能以某种方式使用通用方式?
先感谢您。
这里的示例代码违反了几种TypeScript最佳实践,因此我很难看到您的实际用例是什么:
您创建的所有类型都等同于空类型{}
,因此在结构上彼此相同 。 这在实践中很少需要。 即使这里的代码只是作为一个玩具示例,我怀疑你的意思是A<T>
, B
, Al
和Bl
都完全相同,即使你解决了这个问题,它也可能会导致意想不到的行为在问题中说明。
泛型类A<T>
在结构上不依赖于其类型参数 。 因此,类型A<Al>
与A<Bl>
的类型完全相同,它与A<string>
类型完全相同(您认为A<string>
是不可能的,因为string
不会扩展Al
?嗯,它确实如此,因为Al
是空的......见前面的要点)。 我再次怀疑你的意思是真的。
因此,我将您的示例代码更改为以下内容:
export class A<T extends Al> {
constructor(public t: T) {}
a: string = "a";
}
export class B extends A<Bl> {
constructor(t: Bl) {
super(t);
}
b: string = "b";
}
export class Al {
al: string = "al";
}
export class Bl extends Al {
bl: string = "bl";
}
现在,明确命名的类型实际上在结构上彼此不同,并且泛型类型取决于它们的类型参数。 这并不能解决您所问的问题,但它会阻止编译器在您使用类型时以奇怪的方式运行。
还有一点,我会注意到,但不会改变:
show()
函数在T
是通用的,但似乎不接受或返回任何依赖于类型T
参数。 这很好,因为它只是示例代码,但是编译器无法从show()
的使用中推断出T
(参见文档中的 “类型参数推断”)。 我们只需要在调用它时手动指定T
但在实际代码中,您希望show<T>()
的调用签名依赖于T
(或者您完全从签名中删除T
)。 现在让我们看看发生了什么,并检查你得到的实际编译器错误:
function show<T extends A<Al>>() {
let tGood: Type<A<Al>> = B; // okay
let tBad: Type<T> = B; // error!
// 'B' is assignable to the constraint of type 'T',
// but 'T' could be instantiated with a different
// subtype of constraint 'A<Al>'.
}
所以,正如你所说, tGood
有效。 类型Type<A<Al>>
<A <Al A<Al>
表示“可分配给A<Al>
的事物的构造函数。值B
是完全有效的Type<A<Al>>
<A <Al A<Al>
,因为它是构造函数,它使B
实例,明确定义为扩展A<Al>
。
注意“ X
延伸Y
”,“ X
可分配给Y
”,“ X
比(或者相同) Y
”都说(大约)相同的东西:如果你有一个类型X
的值,你可以将它分配给Y
类型的变量。 扩展/可分配性/狭窄性不是对称的。 如果X
延伸Y
,则Y
延伸X
不一定正确。 例如,考虑将string
赋值给string | number
string | number
(例如, const sn: string | number = "x"
没关系),但是string | number
string | number
不能赋值给string
(例如, const s: string = Math.random()<0.5 ? "x" : 1
是错误)。
所以现在看看tBad
。 这是一个错误,因为T
是一些泛型类型,我们所知道的就是T
扩展了 A<Al>
。 它不一定等于 A<Al>
......并且实际上可以是A<Al>
的严格较窄的子类型(即, T
延伸A<Al>
并不意味着A<Al>
延伸T
)。 所以我们不能将B
分配给Type<A<Al>>
,因为B
的实例可能不会最终成为T
实例。 (错误消息明确说明: 'T' could be instantiated with a different subtype of constraint 'A<Al>'
)
让我们在这里提出一个特定的例子:
export class C extends A<Al> {
constructor(t: Al) {
super(t);
}
c: string = "c";
}
show<C>(); // no error, but B does not create instances of C
您可以看到C
的实例具有名为c
的字符串属性等。 但是B
构造函数不会使用c
属性创建实例。 我可以调用show<C>()
,将T
指定为C
这是被接受的,因为C
扩展了A<Al>
,但是呃哦...在实现的内部, let t: Type<T> = B
本质上是说let t: Type<C> = B
,这是不正确的。 因此错误。
既然我不知道你要用示例代码做什么,我不确定如何建议修复它。 你可以使用你的tGood
,当然......完全删除泛型可以解决问题。 如果您需要继续使用泛型,那么您可能需要传递这些泛型类型的参数。 就是这样的:
function showMaybe<T extends A<Al>>(ctor: Type<T>) {
let tFine: Type<T> = ctor;
}
如果你需要一个Type<T>
你将不得不传递一个...你不能只希望B
是一个。 同样,我不知道上述内容是否适合你,但是类似的东西可能是要走的路。
如果你只是想让编译器警告静音并确信你所做的事实上是安全的,那么你可以使用类型断言 :
function showUnsafe<T extends A<Al>>() {
let tUnsafe: Type<T> = B as any as Type<T>; // unsafe assertion
}
这将不再产生错误,但具有与以前相同的所有问题。 您仍然可以调用showUnsafe<C>()
,并且根据您对tUnsafe
所做的操作,您可能最终会向编译器说谎,当构造实例实际上是B
类时,它是C
类型的实例。 当您使用类型断言时,您将类型安全的责任从编译器转移到您,因此如果断言结果为假并且具有令人不快的运行时结果,您只能责怪自己。
好的,希望这对你有所帮助。 祝好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.