网站搜索

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 的键,对吧?

答案#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
  }
}

你只是通过给它们相同的名字和兼容的函数签名(可选的 todos 参数)来欺骗 TS 认为你在调用相同的 initialize 方法。

你正在尝试做的事情其实应该写成这样:

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

子类与基类之间的连接

这是一种方式。子类知道基类,但基类不需要

这就是抽象类的整个意义。你在基类上声明抽象方法或属性,这实际上是基类所建立的一个契约。哪个子类想要扩展我?先签署契约!遵守我的条款!

回到你的问题,为什么 A 中的 _initialize 要了解 B 的任何事情?也可能有 C 或 D 扩展 A。仅仅从 this 是不可能推断出子类的 keyof 的。

所以当调用 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 中做许多hack的东西,但不要把 JS 和 TS 混为一谈。TS 主要是关于最佳实践,当你违反这些实践时,会遇到各种怪异和错误。你的情况不是 TS 的限制,而是 TS 在试图把你引回正确的轨道。

答案#2

@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 看起来很傻,你不觉得吗?