I have a base Message class and an subclass SpecializedMessaged for extra features
class Message {
constructor(
readonly content: string
) { }
edit(replacement: string) {
return new Message(replacement)
}
}
class SpecializedMessage extends Message {
extra() {}
}
Since the .edit() logic will be the same for Message and all sub-classes, I implement it once in base Message to be inherited. Immutability is important in my use case, so a new instance must be returned every edit.
However,
return new Message(replacement)
will result in an undesirable cast of child instances back to the base type
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()`
I attempt to solve this using this.constructor to automatically use the constructor of whatever class instance is being used
class Message {
// ...
edit(replacement: string) {
return new this.constructor(replacement)
}
}
but TS alerts
This expression is not constructable.
Type 'Function' has no construct signatures.ts(2351)
(property) Object.constructor: Function
despite the code working in vanilla JavaScript.
I am able to quiet the errors by casting this.constructor as a constructor
class Message {
// ...
edit(replacement: string) {
const ctor = this.constructor as { new (content: string): Message}
return new ctor(replacement)
}
}
but want a solution that doesn't require an extra step.
Why is TS rejecting my usage of this.constructor()? How can I solve it?
I understand that the approach is inherently unsafe so long as subclasses can have unique constructor signatures, but that doesn't seem to be what TS is concerned with from the error message.
Edit / Note ... due to some of @Bergi's comments and an additional one from @TmTron
Bergi's and TmTron's proposed type assertions of
this.constructor as typeof Messageandas typeof thisas return type of cause are the golden path one should walk.The OP's implementation would change slightly to ...
... original answer ...
An implementation like ...
... which makes use of
new.targetdoes not raise any compiler warnings.The above code provided in its vanilla flavor works as intended by the OP and looks like this ...