繁体   English   中英

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

[英]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>BAlBl都完全相同,即使你解决了这个问题,它也可能会导致意想不到的行为在问题中说明。

  • 泛型类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.

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