this-typing

It is a common pattern to return the current object (i.e. this) from a method to create fluent-style APIs.For instance, consider the following BasicCalculator module:

  1. export default class BasicCalculator {
  2. public constructor(protected value: number = 0) { }
  3. public currentValue(): number {
  4. return this.value;
  5. }
  6. public add(operand: number) {
  7. this.value += operand;
  8. return this;
  9. }
  10. public subtract(operand: number) {
  11. this.value -= operand;
  12. return this;
  13. }
  14. public multiply(operand: number) {
  15. this.value *= operand;
  16. return this;
  17. }
  18. public divide(operand: number) {
  19. this.value /= operand;
  20. return this;
  21. }
  22. }

A user could express 2 * 5 + 1 as

  1. import calc from "./BasicCalculator";
  2. let v = new calc(2)
  3. .multiply(5)
  4. .add(1)
  5. .currentValue();

This often opens up very elegant ways of writing code; however, there was a problem for classes that wanted to extend from BasicCalculator.Imagine a user wanted to start writing a ScientificCalculator:

  1. import BasicCalculator from "./BasicCalculator";
  2. export default class ScientificCalculator extends BasicCalculator {
  3. public constructor(value = 0) {
  4. super(value);
  5. }
  6. public square() {
  7. this.value = this.value ** 2;
  8. return this;
  9. }
  10. public sin() {
  11. this.value = Math.sin(this.value);
  12. return this;
  13. }
  14. }

Because TypeScript used to infer the type BasicCalculator for each method in BasicCalculator that returned this, the type system would forget that it had ScientificCalculator whenever using a BasicCalculator method.

For instance:

  1. import calc from "./ScientificCalculator";
  2. let v = new calc(0.5)
  3. .square()
  4. .divide(2)
  5. .sin() // Error: 'BasicCalculator' has no 'sin' method.
  6. .currentValue();

This is no longer the case - TypeScript now infers this to have a special type called this whenever inside an instance method of a class.The this type is written as so, and basically means “the type of the left side of the dot in a method call”.

The this type is also useful with intersection types in describing libraries (e.g. Ember.js) that use mixin-style patterns to describe inheritance:

  1. interface MyType {
  2. extend<T>(other: T): this & T;
  3. }