Aron
Aron

Reputation: 9258

How can I specify that a type is an instance of a given class type in TypeScript?

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?

Upvotes: 3

Views: 196

Answers (1)

Aleksey L.
Aleksey L.

Reputation: 38046

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

Upvotes: 2

Related Questions