T<Y> 类型的参数不可分配给 T<X> 类型的参数,其中 Y 是 X 的子类

Argument of type T<Y> is not assignable to parameter of type T<X> where Y is a subclass of X

我的目标是让父 class 定义事件,并让子class 为它们触发事件,但传递给这些事件的类型将取决于子class,但它的基数是 class。很难用语言表达,所以这里是我简化的 TypeScript 代码,它仍然会产生错误:

// The type 'T' not being passed in a function argument fixes it.
type Listener<T> = (e: T) => any
class EventEmitter<T> {
  private listeners: Listener<T>[] = []
  fire(data: T): void {}
}
class Node {
  constructor(readonly graph: Graph<Node>, readonly data: string) {}
}
// Error: Property 'file' is missing in type 'Node' but required in type 'FileNode'.
class FileNode extends Node {
  // Error: Type 'Graph<FileNode>' is not assignable to type 'Graph<Node>'.
  constructor(readonly graph: Graph<FileNode>, readonly file: string, data: string) {
    // Error: Argument of type 'Graph<FileNode>' is not assignable to
    // parameter of type 'Graph<Node>'.
    super(graph, data)
  }
}
abstract class Provider<T extends Node> {
  _onDidCreate = new EventEmitter<T>()
  abstract setup(graph: Graph<T>): void
}
class Graph<T extends Node> {
  // Replacing 'T' for 'any' here makes it work too,
  // but that is obviously not a proper fix.
  constructor(readonly provider: Provider<T>) {}
}

我打算像这样使用它,例如:

import * as vscode from "vscode"
// Error: Type 'FileNode' does not satisfy the constraint 'Node'.
export class VSCodeProvider extends Provider<FileNode> {
  constructor(readonly root: string) {
    super()
  }
  setup(graph: Graph<FileNode>) {
    const watcher = vscode.workspace.createFileSystemWatcher(this.root)
    watcher.onDidCreate((fileUri: vscode.Uri) => {
      this._onDidCreate.fire(new FileNode(graph, fileUri.fsPath, "...data..."))
    })
  }
}

我怎样才能正确修复这些类型错误?我的一部分感觉这些错误通常指向一个糟糕的设计,所以如果有人相反有建议如何解决通过更好的结构来解决这些错误,那就更好了。

每个节点都包含确定图形构建方式的数据,例如应该建立哪些链接,但只有在了解整个图形的情况下才能这样做,因此需要访问图形。我可以交换它并将节点传递给图形,但是节点需要图形中的信息较少,然后图形需要节点来解决问题,所以这似乎是糟糕的设计。该代码似乎受到 provider/graph 和 node/graph.

高度耦合的影响

您可以创建一个它们都实现的接口。 然后你把接口当句柄。

export interface INode{}

export class Node implements INode {...}
export class FileNode implements IProvider {...}

export class VSCodeProvider extends Provider<INode> {...}

或者只检查现有的 类 是否有通用接口。

类型错误的原因竟然是我真正应该知道的。 Arguments in functions following contravariant subtyping:

On the other hand, "function from Animal to String" is a subtype of "function from Cat to String" because the function type constructor is contravariant in the parameter type. Here the subtyping relation of the simple types is reversed for the complex types.

所以鉴于引用中使用的 Animal/Cat 示例,我的问题可以简化为:

class Animal { animal = 1 }
class Cat extends Animal { cat = 1 }
class AnimalFunc<T extends Animal> {
  func: (arg: T) => void
}
class AnimalHandler {
  constructor(func: AnimalFunc<Animal>) {}
}
class CatHandler extends AnimalHandler {
  constructor(func: AnimalFunc<Cat>) {
    // Type 'T' is used contravariant, hence the error:
    // Type 'Animal' is not assignable to type 'Cat'.
    super(func)
  }
}

或者通过立即传递函数进一步简化:

class Animal { animal = 1 }
class Cat extends Animal { cat = 1 }
class AnimalHandler {
  constructor(func: (arg: Animal) => void) {}
}
class CatHandler extends AnimalHandler {
  constructor(func: (arg: Cat) => void) {
    // Type 'T' is used contravariant, hence the error:
    // Type 'Animal' is not assignable to type 'Cat'.
    super(func)
  }
}

解决方案是更改 Graph 以不再将提供程序作为 属性,而是仅在需要时将其作为函数参数传递给它。由于图仅需要提供者来设置事件回调,因此将其作为参数提供就足够了。例如,Graph 变为:

class Graph<T extends Node> {
  provide(provider: Provider<T>) {}
}