How can I get names of uninitialized static properties in a class before ESNext?

Viewed 253

I have a typescript class with several static properties leave uninitialized such as:

class A {
    static foo:number
    static bar:number
}

for(const key in A) {
    console.log(key)
}

If I compile this code in target ESNext everything is fine and all the keys would be logged.

However, if I change the target to any previous, even ES2020, I will get an empty output.

When I dig into the generated javascript code I could see all the properties had been removed:

"use strict";
class A {
}
for (const key in A) {
    console.log(key);
}

How can I keep these properties before ESNext?

3 Answers

I believe the answer to your question is likely 'no'.

Typescript can useDefineForClassFields, which I think is currently correct for esnext in this case (src: https://github.com/microsoft/TypeScript/issues/43113).

But es2021 still does not have static fields. src: https://github.com/tc39/proposal-class-fields -- they are stage 3 (at least according to that proposal page).

reproduction of your issue: https://www.typescriptlang.org/play?module=5&ts=4.3.4#code/MYGwhgzhAECC0G8BQ1XQgFzBglsaAZgPZEBcAdgK4C2ARgKYBOA3CmptntLWIxTQxZIAvkiTAi5CERD0AdCCIBzABQByAA6MiGImoA00APK0AVvWAY5S+hiMB3cgAVtGphgCeAEXoRgjHA1dRggVWDktHR0PNwBKWLFiRhUJKQxoAGt6D2gccjhYxDZUVOlZBWUVLI8E4SA

Note that you can get close with static #foo, and in a way closer with static ##foo .. but neither of these will execute in the target context ("private fields are not currently supported"). This is just abusing the ongoing partial/increasing support for class fields and private fields.

Just assign static property to some initialize value

class A {
    static foo:number = 1
    static bar:number = 2
}

PlaygroundLink on ES2015

You should explicitly set your properties to undefined (or null if you prefer):

class A {
  static foo: number | undefined = undefined; // same as uninitialzied value
  static bar: number | undefined = undefined;
}

Reasoning: In addition to preserving your properties after compilation, this is a more accurate type-definition if you are not initializing your property. As long as you declare a variable or property without initialization (e.g. let a: number;), you create a situation where that variable or property could be used before initialization and therefore its value will be undefined:

class A {
    static foo: number // will be `undefined` until initialization.
    static bar: number | undefined = undefined;
}

A.foo.toString() // No compiler error, but fails at runtime. Bad!
A.bar.toString() // Produces a compiler error. Good!

If you don't want to deal with undefined or null values, you should initialize your class variables to satisfy the number type constraint:

class A {
  static foo: number = 0;
  static bar: number = 0;
}

Full Playground Example

Related