我有一个基类
Message
和一个用于额外功能的子类 SpecializedMessaged
class Message {
constructor(
readonly content: string
) { }
edit(replacement: string) {
return new Message(replacement)
}
}
class SpecializedMessage extends Message {
extra() {}
}
由于
.edit()
逻辑对于 Message
和所有子类都是相同的,因此我在基类 Message
中实现一次以进行继承。不变性在我的用例中很重要,因此每次编辑都必须返回一个新实例。
但是,
return new Message(replacement)
将导致子实例不期望地转换回基本类型
let message = new SpecializedMessage('hello')
message.extra() // works
message = message.edit('world') // message now instanceof `Message` instead of `SpecializedMessage`
message.extra() // fails, `Message` lacks `.extra()`
我尝试使用
this.constructor
来解决这个问题,以自动使用正在使用的任何类实例的构造函数
class Message {
// ...
edit(replacement: string) {
return new this.constructor(replacement)
}
}
但是 TS 警报
This expression is not constructable.
Type 'Function' has no construct signatures.ts(2351)
(property) Object.constructor: Function
尽管代码可以在普通 JavaScript 中运行。
我可以通过转换
this.constructor
as
构造函数来消除错误
class Message {
// ...
edit(replacement: string) {
const ctor = this.constructor as { new (content: string): Message}
return new ctor(replacement)
}
}
但想要一个不需要额外步骤的解决方案。
为什么 TS 拒绝我使用
this.constructor()
?我该如何解决?
我知道,只要子类可以具有唯一的构造函数签名,这种方法本质上就是不安全的,但从错误消息来看,这似乎不是 TS 所关心的。
编辑/注释 ...由于一些@Bergi的评论
Bergi 提出的类型断言
this.constructor as typeof Message
原因是人们应该走的黄金道路。
OP 的实现将略有改变......
class Message {
constructor(
readonly content: string
) {
}
edit(replacement: string) {
return new (this.constructor as typeof Message)(replacement);
}
}
class SpecializedMessage extends Message {
extra() {}
}
... 原答案 ...
类似的实现 ...
class Message {
constructor(
readonly content: string,
public messageType = new.target,
) {
}
edit(replacement: string) {
return new this.messageType(replacement)
}
}
class SpecializedMessage extends Message {
extra() {}
}
new.target
不会引发任何编译器警告。
上面以香草风格提供的代码按照OP的预期工作,看起来像这样......
class Message {
constructor(content, messageType = new.target) {
this.content = content;
this.messageType = messageType;
}
edit(replacement) {
return new this.messageType(replacement);
}
}
class SpecializedMessage extends Message {
extra() {}
}
const defaultMsg = new Message("default message");
const specialMsg = new SpecializedMessage("special message");
console.log(
specialMsg.edit(`yet another ${ specialMsg.content }`).content
);
console.log(
specialMsg.edit(`yet another ${ specialMsg.content }`) instanceof Message
);
console.log(
specialMsg.edit(`yet another ${ specialMsg.content }`) instanceof SpecializedMessage
);
console.log(
defaultMsg.edit(`yet another ${ defaultMsg.content }`).content
);
console.log(
defaultMsg.edit(`yet another ${ defaultMsg.content }`) instanceof Message
);
console.log(
defaultMsg.edit(`yet another ${ defaultMsg.content }`) instanceof SpecializedMessage
);
.as-console-wrapper { min-height: 100%!important; top: 0; }