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