Search code examples
typescriptecmascript-5

How can I make old style classes work in typescript?


While converting a large number of files to typescript, I have many classes declared this way.

function FooClass() {
    this.bar = 1; // error TS2683: 'this' implicitly has type 'any'
                  // because it does not have a type annotation.
}

FooClass.prototype.myMethod = function() {
    // ...
}

How can I make this work with strict type checking turned on, while avoiding rewriting it all using class syntax?


Solution

  • The easiest way to get the above code to work is to add a this parameter to the function, like so:

    function FooClass(this: {bar: number}) {
        this.bar = 1; // okay
    }
    

    Unfortunately, you will soon find that the compiler doesn't know what to make of FooClass when you treat it like a constructor:

    const oops = new FooClass(); // error, oops is implicitly any
    oops.bar // okay but compiler has no idea
    oops.myMethod() // okay but also no idea
    oops.foo // also okay, but you probably don't want it to be
    oops.yourMethod() // ditto
    

    This is apparently by design. The best way to annotate this, in my opinion, is to define the types FooClass and FooConstructor in advance:

    interface FooClass {
      bar: number;
      myMethod(): void;
    }
    
    interface FooConstructor {
      new(): FooClass,
      prototype: FooClass
    }
    

    Note that when you use the class FooClass {} way of creating constructors, TypeScript automatically generates both a value FooClass which is the constructor itself, and a type FooClass which is the type of the instances created by the constructor. This is often confusing to developers, so take care. We are doing that here manually: the above interface FooClass is the type, not the value, which we are about to create.

    After you define those types, assert that the FooClass function is of type FooConstructor when you create it (the assertion needs to pass though Function or any and is not safe, so be careful).

    const FooClass = function FooClass(this: FooClass) {
      this.bar = 1;
    } as Function as FooConstructor;
    
    FooClass.prototype.myMethod = function () {
      // ...
    }
    

    And test it out:

    const okay = new FooClass();
    okay.bar // number
    okay.myMethod() // known
    okay.foo // error, yay
    okay.yourMethod() // error, yay
    

    Hope that helps; good luck!