Random TIL about `class` Typescript

Apr 10, 2023

Static init block

Static blocks allow you to write complex init value for static props directly inside the class, no need writing messy setup code outside the class.

class Foo {
  static bar = 0;
  static {
    Foo.bar = complexInit(); // 123
  }
}
console.log(Foo.bar); // output = 123

Private prop

TypeScript has two ways to declare private fields:

  • private keyword: Only checked at compile time, but doesn't hide data at runtime.
  • # prefix: Truely hide data, this is native Javascript.
class Foo {
  private fake = 'fake';
  #real = 'real';
}
const f = new Foo();
console.log(f.fake); // output = "fake"
console.log(f.real); // output = undefined

this type

If you are building a fluent interface [1] API, where you chain a bunch of methods like this obj.doA().doB(), your TS compiler might slap you much when extending class. If BaseClass return its own type, child class methods might return the BaseClass type instead of ChildClass.

class Foo {
  doA(): Foo {
    return this;
  }
}
 
class ChildFoo extends Foo {
  doB(): this {
    return this;
  }
  doC() {}
}
 
const obj = new ChildFoo();
obj
  .doA()
  .doB() //Property 'doB' does not exist on type 'Foo'.
  .doC();
class Foo {
  doA(): this {
    return this;
  }
}
 
const obj = new ChildFoo();
obj
  .doA()
  .doB() //Worked
  .doC();

I don't like this language design. this belongs to value space, if you want to move it to type space, you should use typeof operator. Like this:

class Foo {
  doA(): typeof this {
    return this;
  }
}

More reading: this Types

Footnotes
  1. [1]

    https://en.wikipedia.org/wiki/Fluent_interface