'How to write typescript with constructor function/new keyword?

This is an example from MDN docs for the usage of new keyword

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car('Eagle', 'Talon TSi', 1993);

I believe, the TS version of the function would be:

/* car type */
type CarDetails = {
    make: string;
    model: string;
    year: number;
}

/* setting this to type CarDetails */
function Car(this:CarDetails, make: string, model:string, year:number) {
  this.make = make;
  this.model = model;
  this.year = year;
}

This gives me intellisense/autocomplete while writing the method but how do I make TS infer types while creating an instance with new keyword. Even after typescripting the method, creating an instance like this:

const car1 = new Car('Eagle', 'Talon TSi', 1993);

still keeps car1 as type any

How to make car1's type infer to be Car method's this type ?

(without explicitly setting type for the instance object, const car1: CarDetails = new Car(...; )



Solution 1:[1]

You could use classes instead:

class Car {

  make: string;
  model: string;
  year: number;
  
  constructor(make: string, model: string, year: number) {
    this.make = make;
    this.model = model;
    this.year = year;
  }
}

// usage is the same

const car1 = new Car('Eagle', 'Talon TSi', 1993);

Solution 2:[2]

Obviously the class syntax should be the way to go, but as you I'm also running in circles trying to find the right solution that outputs the old valid JS syntax. This is a good approximation that I've found in this random answer and it's poorly explained in the official Typescript documentation. Here is the relevant part that I've modified to make it better:


// Only instance members here.
export interface Circle { // Name the interface with the same than the var.
  radius: number;
  area: () => number;
  perimeter: () => number;
}

// You could define static members here.
interface CircleConstructor {
  new(radius: number): Circle;
}

export const Circle = function(this: Circle, radius: number) {
  const pi = 3.14;
  this.radius = radius;
  this.area = function () {
    return pi * radius * radius
  }
  this.perimeter = function () {
    return 2 * pi * radius;
  }
} as unknown /*as any*/ as CircleConstructor; // Note the trust-me casting
    
const c = new Circle(3); // okay

This comes with some issues. For instance, the fact that it's using any type which is forbidden or the necessary use of the as operator which is mega-ugly. As improvement, I've used unknown instead of any but the core problem remains. This makes the lint tool to not complain, so it's a big improvement over the any.

This is the best solution I've found so far. Typescript devs are aware of this, but they have decided to not provide support for this kind of "syntax".

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 User81646
Solution 2