0

I tried to create a base class that has a static utility method to construct the class, which uses return new this(). But it doesn't work when I have a subclass extending it, because that utility method returns the base class instead of subclass, which is not the same as JavaScript.

Minimal Example:

class Base {
    static create() {
        return new this()
    }
}
class Sub extends Base {
    fn() {
        return this
    }
}
Sub.create().fn() // TypeScript error, JavaScript ok
Base.create().fn() // TypeScript error, JavaScript error

TypeScript Playground

1 Answer 1

3

It's because TypeScript doesn't have polymorphic this types for static methods. There's a longstanding open issue, microsoft/TypeScript#5863, asking for such a feature. For now it's not part of the language, but luckily comments on that issue describe a workaround that should get you the behavior you're looking for:

class Base {
    static create<T extends Base>(this: new () => T) {
        return new this()
    }
}

Here we are making create() a generic method with a parameter of type T extends Base that can only be called on an object whose this context is a zero-arg constructor that returns a value of type T.

Therefore the compiler knows that Sub.create() will return a Sub and that Base.create() will return a Base:

Sub.create().fn() // okay
Base.create().fn() // Property 'fn' does not exist on type 'Base'

This also has the advantage over your existing code in that the following is an error now:

class Oops extends Base {
    x: string;
    constructor(x: string) {
        super();
        this.x = x.toUpperCase();
    }
}

Oops.create(); // error!

The Oops class does not have a zero-arg constructor, and so the implementation of Oops.create() will end up calling new Oops() without the required parameter and cause a runtime error when it tries to call toUpperCase() on undefined.


Okay, hope that helps; good luck!

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.