将类类型分配给泛型变量时,Typescript编译错误

时间:2019-07-08 10:35:20

标签: angular 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示例here,以便您了解其工作原理。

我希望如果B extends A<Al>T extends A<Al>,通用版本let t: Type<T>; t = B;可以正常工作。相反,我必须使用let t: Type<A<Al>>; t = B;,我想知道是否可以以某种方式使用通用方式?

谢谢。

1 个答案:

答案 0 :(得分:1)

此处的示例代码违反了几种TypeScript最佳实践,因此我很难看到您的实际用例是什么

  • 您创建的所有类型都等同于空类型{},因此是structurally equivalent to each other。在实践中,这是非常罕见的。即使这里的代码只是作为一个玩具示例,我也怀疑您是指A<T>BAlBl完全相同,这是即使您解决了问题中提到的问题,也很可能会导致意外的行为。

  • 通用类A<T> does not depend structurally on its type parameter。因此,类型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中是通用的,但似乎不接受或返回任何取决于类型T的参数。这很好,因为它只是示例代码,但是编译器无法通过使用show()来推断T(请参见the documentation中的“类型参数推断”)。调用时,我们只需手动指定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>的意思是“可分配给B的事物的构造方法。值Type<A<Al>>是完全有效的B,因为它是构造方法,并且使{明确定义为扩展A<Al>的{​​1}}个实例。

请注意,“ X扩展了Y”,“ X可分配给Y”和“ X窄于(或等于)Y”都(近似)说同一件事:如果您拥有类型X的值,则可以将其分配给类型Y的变量。并且扩展/可分配性/狭窄度不是对称的。如果X扩展了Y,则Y扩展了X不一定是正确的。作为示例,请考虑将string分配给string | number(例如,const sn: string | number = "x"可以),但是不能将string | number分配给string(例如,{ {1}}是一个错误)。

所以现在看const s: string = Math.random()<0.5 ? "x" : 1。这是一个错误,因为tBad是某种通用类型,而我们所知道的只是T 扩展 T。它不一定与A<Al>相等。实际上,它可能是A<Al>的严格狭窄子类型(即A<Al>扩展了{{1} }并不意味着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 assertion

Type<T>

这将不再产生错误,但是具有与以前相同的所有问题。您仍然可以调用B,并且根据对function showUnsafe<T extends A<Al>>() { let tUnsafe: Type<T> = B as any as Type<T>; // unsafe assertion } 的处理方式,您可能最终会向编译器撒谎,即构造实例的类型实际上为{{ 1}}。使用类型断言时,您将类型安全的责任从编译器转移到您身上,因此,只有当断言为假并造成不愉快的运行时后果时,您才应责怪自己。


好的,希望这对您有所帮助。祝你好运!

Link to code