简体   繁体   English

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

[英]Typescript compile error when assigning class type to a generic variable

I am trying to set a class type to a generic variable in typescript. 我试图将类型设置为typescript中的泛型变量。 It shows compile error when using generic version. 它使用通用版本时显示编译错误。

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;
}

I have also created a stackblitz example here so you can see how it works. 我还在这里创建了一个stackblitz示例以便您可以看到它是如何工作的。

I expect that if B extends A<Al> and T extends A<Al> , generic version let t: Type<T>; t = B; 我希望如果B extends A<Al>并且T extends A<Al> ,则通用版本let t: Type<T>; t = B; let t: Type<T>; t = B; to work. 上班。 Instead I have to use let t: Type<A<Al>>; t = B; 相反,我必须使用let t: Type<A<Al>>; t = B; let t: Type<A<Al>>; t = B; and I am wondering if I can somehow use generic way? 我想知道我是否能以某种方式使用通用方式?

Thank you in advance. 先感谢您。

The example code here is violating several TypeScript best practices so it's hard for me to see what your actual use case is: 这里的示例代码违反了几种TypeScript最佳实践,因此我很难看到您的实际用例是什么:

  • All of the types you created are equivalent to the empty type, {} , and are therefore structurally equivalent to each other . 您创建的所有类型都等同于空类型{} ,因此在结构上彼此相同 This is very rarely desirable in practice. 这在实践中很少需要。 Even if the code here is just meant as a toy example, I doubt you mean for A<T> , B , Al and Bl to all be completely identical, and it is likely going to result in unexpected behavior even if you address the problem stated in the question. 即使这里的代码只是作为一个玩具示例,我怀疑你的意思是A<T>BAlBl都完全相同,即使你解决了这个问题,它也可能会导致意想不到的行为在问题中说明。

  • The generic class A<T> does not depend structurally on its type parameter . 泛型类A<T> 在结构上不依赖于其类型参数 Therefore, the type A<Al> is exactly the same type as A<Bl> which is exactly the same type as A<string> (did you think A<string> was impossible because string does not extend Al ? Well, it does, since Al is empty... see previous bullet point). 因此,类型A<Al>A<Bl>的类型完全相同,它与A<string>类型完全相同(您认为A<string>是不可能的,因为string不会扩展Al ?嗯,它确实如此,因为Al是空的......见前面的要点)。 Again, I doubt you mean for this to be true. 我再次怀疑你的意思是真的。

Therefore I am going to alter your example code to the following: 因此,我将您的示例代码更改为以下内容:

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";
}

Now the distinctly named types actually differ structurally from each other and the generic types depend on their type parameters. 现在,明确命名的类型实际上在结构上彼此不同,并且泛型类型取决于它们的类型参数。 This doesn't solve the problem you're asking about, but it does stop the compiler from behaving in bizarre ways when you use your types. 这并不能解决您所问的问题,但它会阻止编译器在您使用类型时以奇怪的方式运行。

One more that I'll note but won't change: 还有一点,我会注意到,但不会改变:

  • Your show() function is generic in T but doesn't seem to accept or return any parameters dependent on the type T . 你的show()函数在T是通用的,但似乎不接受或返回任何依赖于类型T参数。 This is fine since it's just example code, but there's no way for the compiler to infer T from the usage of show() (see "type argument inference" in the documentation ). 这很好,因为它只是示例代码,但是编译器无法从show()的使用中推断出T (参见文档 “类型参数推断”)。 We'll just have to specify T manually when we call it. 我们只需要在调用它时手动指定T But in real-world code you'd want the call signature of show<T>() to depend on T (or you'd remove T from the signature entirely). 但在实际代码中,您希望show<T>()的调用签名依赖于T (或者您完全从签名中删除T )。

Now let's look at what's happening and examine the actual compiler error you get: 现在让我们看看发生了什么,并检查你得到的实际编译器错误:

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>'.
}

So, tGood works, as you noted. 所以,正如你所说, tGood有效。 The type Type<A<Al>> means "a constructor of things which are assignable to A<Al> . The value B is a perfectly valid Type<A<Al>> , because it is a constructor, and it makes B instances, which are explicitly defined to extend A<Al> . 类型Type<A<Al>> <A <Al A<Al>表示“可分配给A<Al>的事物的构造函数。值B是完全有效的Type<A<Al>> <A <Al A<Al> ,因为它是构造函数,它使B实例,明确定义为扩展A<Al>

Note that " X extends Y ", " X is assignable to Y ", and " X is narrower than (or the same as) Y " are all saying (approximately) the same thing: if you have a value of type X , you can assign it to a variable of type Y . 注意“ X延伸Y ”,“ X可分配给Y ”,“ X比(或者相同) Y ”都说(大约)相同的东西:如果你有一个类型X的值,你可以将它分配给Y类型的变量。 And extension/assignability/narrowerness is not symmetric. 扩展/可分配性/狭窄性不是对称的。 If X extends Y , it is not necessarily true that Y extends X . 如果X延伸Y ,则Y延伸X不一定正确。 As an example, consider that string is assignable to string | number 例如,考虑将string赋值给string | number string | number (eg, const sn: string | number = "x" is okay), but string | number string | number (例如, const sn: string | number = "x"没关系),但是string | number string | number is not assignable to string (eg, const s: string = Math.random()<0.5 ? "x" : 1 is an error). string | number不能赋值给string (例如, const s: string = Math.random()<0.5 ? "x" : 1是错误)。

So now look at tBad . 所以现在看看tBad That's an error, because T is some generic type, and all we know about it is that T extends A<Al> . 这是一个错误,因为T是一些泛型类型,我们所知道的就是T 扩展了 A<Al> It is not necessarily equal to A<Al> ... and in fact may be a strictly narrower subtype of A<Al> (that is, T extends A<Al> does not imply that A<Al> extends T ). 它不一定等于 A<Al> ......并且实际上可以是A<Al>的严格较窄的子类型(即, T延伸A<Al>并不意味着A<Al>延伸T )。 So we can't assign B to Type<A<Al>> , because the instances of B might not end up being instances of T . 所以我们不能将B分配给Type<A<Al>> ,因为B的实例可能不会最终成为T实例。 (The error message explicitly says this: 'T' could be instantiated with a different subtype of constraint 'A<Al>' ) (错误消息明确说明: 'T' could be instantiated with a different subtype of constraint 'A<Al>'

Let's come up with a particular example here: 让我们在这里提出一个特定的例子:

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

You can see that an instance of C has, among other things, a string property named c . 您可以看到C的实例具有名为c的字符串属性等。 But the B constructor doesn't make instances with a c property. 但是B构造函数不会使用c属性创建实例。 I can call show<C>() , specifying T as C . 我可以调用show<C>() ,将T指定为C That is accepted because C extends A<Al> , but uh oh... inside of the implementation, let t: Type<T> = B is essentially saying let t: Type<C> = B , which is just not true. 这是被接受的,因为C扩展了A<Al> ,但是呃哦...在实现的内部, let t: Type<T> = B本质上是说let t: Type<C> = B ,这是不正确的。 Hence the error. 因此错误。

Now since I can't tell what you're trying to do with the example code, I'm not sure how to suggest fixing it. 既然我不知道你要用示例代码做什么,我不确定如何建议修复它。 You can use your tGood , of course... removing generics entirely should solve the problem. 你可以使用你的tGood ,当然......完全删除泛型可以解决问题。 If you need to keep using generics, then you will probably need to pass in parameters of those generic types. 如果您需要继续使用泛型,那么您可能需要传递这些泛型类型的参数。 That is, something like this: 就是这样的:

function showMaybe<T extends A<Al>>(ctor: Type<T>) {
  let tFine: Type<T> = ctor;
}

If you need a Type<T> you're going to have to pass one in... you can't just hope that B is one. 如果你需要一个Type<T>你将不得不传递一个...你不能只希望B是一个。 Again, I don't know if the above works for you, but something like it is probably the way to go. 同样,我不知道上述内容是否适合你,但是类似的东西可能是要走的路。

If you just want to silence the compiler warning and are convinced that what you are doing is, in fact, safe, then you can use a type assertion : 如果你只是想让编译器警告静音并确信你所做的事实上是安全的,那么你可以使用类型断言

function showUnsafe<T extends A<Al>>() {
  let tUnsafe: Type<T> = B as any as Type<T>; // unsafe assertion
}

This will no longer produce an error, but has all the same problems as before. 这将不再产生错误,但具有与以前相同的所有问题。 You can still call showUnsafe<C>() , and depending on what you do with tUnsafe you may end up lying to the compiler that a constructed instance is of type C when it is really of type B . 您仍然可以调用showUnsafe<C>() ,并且根据您对tUnsafe所做的操作,您可能最终会向编译器说谎,当构造实例实际上是B类时,它是C类型的实例。 When you use type assertions, you shift the responsibility for type safety from the compiler to you, so you only have yourself to blame if the assertion turns out to be false and have unpleasant runtime consequences. 当您使用类型断言时,您将类型安全的责任从编译器转移到您,因此如果断言结果为假并且具有令人不快的运行时结果,您只能责怪自己。


Okay, hope that made sense and is of some help to you. 好的,希望这对你有所帮助。 Good luck! 祝好运!

Link to code 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM