It’s June 2022 and a new version of ECMAScript has just been released. One of the novelties that this version has brought us are the class fields.
You see, Object-Oriented languages usually have this feature, where you can declare a field on a class in the top-most scope of the class like this:
class Foobar {
private someNumberField = 10;
public aFieldWithNoValue;
}
Well, aside from the obvious lack of access keywords, this was still impossible in JavaScript. Until ES2022 you usually set those in the constructor (or never mentioned them at all):
class Foobar {
constructor() {
this.someNumberField = 10;
}
}
But because of the proposal1 that has been marinating since 2017, this feature has been brought to life in ES2022, and since then, JS is now like any other OO language.
Hehe.
Well, it has been decided around 2018/2019 that the declaration of class fields will be done in JS using [[Define]]
semantics and not [[Set]]
semantics.
This means that whenever a class Foobar defines a property (for example, initialized like: someNumberField = 10), JS is
- not doing
this.someNumberField = 10
when creating an instance of the class
but
- running
Object.defineProperty(Foobar, "someProp", 10)
somewhere around the class declaration
Why does it matter? Oh boy, it matters a lot. See the heated discussion here.
Most importantly to the TS users, the class fields that were in TypeScript long before ES2022, were originally implemented with [[Set]]
semantics.
That’s what the TypeScript creators assumed at that time, and I have to admit, [[Set]]
semantics make total sense to me.
Sooo, since TS 3.7 there’s this useDefineForClassFields
flag that when turned on, flips the implementation to an ECMAScript-compilant one. 2
But the new ECMAScript-compliant behaviour breaks some existing code related to class inheritance and property setters.
If you have a derived class that narrows a type of some property (a totally valid thing to do in TS) that is set in the base class with a super()
call, it will be set to undefined
after the constructor of the derived class returns!
There are a couple of fixes:
-
quick and dirty: set
useDefineForClassFields
tofalse
if you have it set totrue
. Unless your target isES2022
or higher, then you’re left with the other option: -
fix the code by marking the property in the derived class with the
declare
keyword. Even if originally you overrode it. -
or use a transpiler that does provides non-compliant transformation options: https://swc.rs/docs/configuration/compilation#jsctransformusedefineforclassfields