3

I want to make a Constructor type that takes a class as a type parameter and returns the signature of its constructor.

This would be useful in defining a static property make method on a class that can create an instance of that class rather than creating it with a new ClassInstance(...) statement.

This is what I've got so far:

type ClassConstructorArgs<T> = T extends { new (...args: infer A): any }
  ? A
  : never;

type Constructor<T> = (...args: ClassConstructorArgs<T>) => T;

class Person {
  constructor(public name: string, public age: number) {}

  static make: Constructor<typeof Person> = (...args) => new Person(...args);
  //                                                     ^^^^^^^^^^^^^^^^^^^
  // Type 'Person' is missing the following properties from type 'typeof Person': prototype, make
}

The problem is that the static make has an error: Type 'Person' is missing the following properties from type 'typeof Person': prototype, make

I understand that this is because my Constructor type is wrong, constructors of class T don't return the class T itself but an instance of T.

But then I don't know how I can express that Constructor returns an instance of T rather than the class T itself. Is that even possible in TypeScript?

1 Answer 1

2

Typescript has builtin utilities both for ConstructorParameters and InstanceType:

type Constructor<T extends new (...args: any) => any> = 
  (...args: ConstructorParameters<T>) => InstanceType<T>;

class Person {
  constructor(public name: string, public age: number) {}

  static make: Constructor<typeof Person> = (...args) => new Person(...args);
}

const p = Person.make('some name', 1); // p is of type Person

Playground


If you wonder how these utilities are defined and what's wrong with your attempt, here you go:

type ConstructorParameters<T extends new (...args: any) => any> = 
  T extends new (...args: infer P) => any ? P : never;

type InstanceType<T extends new (...args: any) => any> = 
  T extends new (...args: any) => infer R ? R : any;

Source

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

2 Comments

Interesting, I had no idea! Clever how the InstanceType is defined. Do you know why in both types the type constraints are duplicated in the parameter declaration and inside the type body? It seems redundant
I don't know why it is duplicated, but one could define something like type Newable = new (...args: any) => any; and reuse it

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.