如何修复“类型的参数不能分配给类型的参数”错误?

时间:2019-04-27 17:17:42

标签: typescript

给予

abstract class A {
  constructor() {
    this.initialize()
  }

  initialize<T extends {
    [t in keyof this]?: boolean
  }>(todos?: T) {
    // Do something
  }
}

class B extends A {
  initialize() {
    super.initialize({ // <-- it will throw error starting on this open bracket
        test: false
    })
  }

  test() {
    return 'test'
  }
}

为什么上面的代码抛出错误说明 { test: false }不能分配给{[t in keyof this]?: boolean}吗?虽然很明显。

'test'是B的键之一,对吧?并且keyof this将指向B的键,对吧?

2 个答案:

答案 0 :(得分:1)

正确的抽象类定义

首先,您在“作弊”。

abstract class A {
  constructor() {
    this.initialize()
    //   ~~~~~~~~~~ this `initialize` is from subclass
  }

  // different from this one here
  initialize<T extends {
    [t in keyof this]?: boolean
  }>(todos?: T) {
    // Do something
  }
}

通过给TS提供相同的名称和兼容的函数签名(可选的initialize参数),您就可以欺骗TS认为您正在调用相同的todos方法。

您要执行的操作实际上应写为:

abstract class A {
  constructor() {
    this.initialize()
  }

  // fix 1: delcare abstract method
  abstract initialize(): void

  // fix 2: rename, mark as protected
  protected _initialize<T extends {
    [t in keyof this]?: boolean
  // fix 3: todos probably isn't optional
  }>(todos: T) {
    // Do something
  }
}

class B extends A {
  initialize() {
    super._initialize({
        test: false
    })
  }

  test() {
    return 'test'
  }
}

子类与基类之间的连接

这是一种方式。子类知道基类,但是不是有义务知道所有扩展自身的子类。因为每个子类可以实现不同的东西,所以基类应该如何知道所有这些信息?

这就是抽象类的全部内容。您在基类上声明抽象方法或属性,这实际上是基类建立的契约。哪个子类想扩展我?先签合同!遵守我的条款!

回到您的情况,为什么_initialize中的A应该知道有关B的任何信息?也可能有CD扩展了A。仅从keyof 推断 this子类是不可能的。

因此,在调用时,您需要告诉super._initialize哪些子类是this。这是必要条件,而不是TS限制。

abstract class A {
  constructor() {
    this.initialize()
  }

  abstract initialize(): void

  // pass subclass type as generic param B
  protected _initialize<B>(todos: {
    [t in keyof B]: boolean
  }) {
    // Do something
  }
}

class B extends A {
  initialize() {
    super._initialize<B>({
      test: true,  // <-- this is correct now
      foobar: true // <-- this triggers error report
    })
  }

  test() {
    return 'test'
  }
}

您可以在JS中做很多修改工作,但不要将JS与TS混淆。 TS主要是关于最佳实践的,当您破坏它们时会遇到各种各样的怪癖和错误。您的情况不是TS的限制,而是TS试图说服您回到正确的轨道。

答案 1 :(得分:0)

@JGoodgive那我如何才能真正引用当前课程?因为我需要它指向当前班级,以便以后在班级的后代中使用。我可以通过以下方式做到这一点:

abstract class A<Model> {
  constructor() {
    this.initialize()
  }

  initialize<T extends {
    [t in keyof Model]?: boolean
  }>(todos?: T) {
    // Do something
  }
}

class B extends A<B> {
  initialize() {
    super.initialize({
      test: true,
      initialize: false,
    })
  }

  test() {
    return 'test'
  }
}

但是将B传给B似乎很愚蠢,你不觉得吗?