Home > front end >  Typescript: use `this` type inside a class constructor
Typescript: use `this` type inside a class constructor

Time:09-04

I have the following class with a constructor that defines properties on it. I need the constructor to accept an object which only contains the (extending) class members

abstract class A {
    constructor(initializer: Partial<this>) { // <-- using this type in a constructor is not allowed!
        for (const [key,value] of Object.entries(initializer)) {
            Object.defineProperty(this, key, { value, enumerable: true })
        }
    }
    static someStaticMethod() {}
    someInstancemethod() {}
    
}

this class is only used to be extended by other classes

class B extends A {
    a?: string
    b?: number
    c?: boolean
}

new B({ a: "hello", b: 1, c: true, d: "this must error" }) // <-- I want only properties of B here in the constructor argument

I know I can add a type argument to A and then assign the extending class to it

abstract class A<T> {
    constructor(initializer: Partial<T>) {
        ...
    }
}

class B extends A<B> {
...
}

But this solution of repeating B doesn't look elegant, especially considering that this is an external API for a library I'm working on. Is it possible to achieve what I want, leaving the syntax as in the first example: class B extends A? If not, what is a possible workaround? Playground Link

CodePudding user response:

It is not currently possible in TypeScript to use this types in the call signature for a class constructor() method. There is an open feature request at microsoft/TypeScript#38038 asking for such support, but for now it's not part of the language.

(A comment asked why it isn't already supported, but I don't have an authoritative answer for that. I can guess: The constructor method is unique in that it is a static method whose this context is that of a class instance, so presumably implementing this typing for its call signature would have required some conscious effort to do. And it's not a feature the community is clamoring for, as evidenced by the relatively few upvotes on the issue linked above. Those are probably contributing factors for why it is not already supported. But, as I said, these are guesses.)


Until and unless that is implemented you'd need to use workarounds.

One such workaround would be to use a static method with this types instead of the constructor, so you call B.make({}) instead of new B({}).

Unfortunately this types are also not supported for static methods. Support is requested at microsoft/TypeScript#5863. But you can simulate such behavior with this parameters and a generic type parameter, as mentioned in this comment on that GitHub issue:

abstract class A {
    static make<T extends A>(
      this: new (initializer: Partial<A>) => T, 
      initializer: Partial<T>
    ) {
        return new this(initializer);
    }
    constructor(initializer: Partial<A>) {
        for (const [key, value] of Object.entries(initializer)) {
            Object.defineProperty(this, key, { value, enumerable: true })
        }
    }
    static someStaticMethod() { }
    someInstancemethod() { }

}

The make() method can only be called on a concrete constructor which accepts a Partial<A> and produces an instance of type T constrained to A. So you can't call A.make() directly for the same reason you can't call new A(), since the constructor is abstract:

A.make({}) // error!
// <-- Cannot assign an abstract constructor type to a non-abstract constructor type

But you can subclass as desired, and the static make() method will be inherited:

class B extends A {
    a?: string
    b?: number
    c?: boolean
}

When you call B.make(), then, the compiler infers that T is B and so the parameter to make() is Partial<B>, which, among other things, will reject excess properties:

const b = B.make({ a: "hello", b: 1, c: true }); // okay
// const b: B
console.log(b); // {a: "hello", b: 1, c: true}

B.make({ a: "hello", b: 1, c: true, d: "this must error" }); // error!
// -------------------------------> ~~~~~~~~~~~~~~~~~~~~
// Object literal may only specify known properties

Playground link to code

  • Related