You have the ability to describe and declare a class constructor in the following manner:
interface CarConstructor {
new <T>(): Car<T | undefined>;
new <T>(maxSpeed: T): Car<T>;
}
interface Car<T> {
maxSpeed: T
}
declare const Car: CarConstructor;
const car1 = new Car<number>(1);
car1.maxSpeed // maxSpeed is of type number
const car2 = new Car<number>();
car2.maxSpeed // maxSpeed is of type number | undefined
A CarConstructor
comes with two construct signatures that are overloaded as per your requirements.
The challenge arises when attempting to implement this specific CarConstructor
. The current restriction in TypeScript prevents adding type parameters to class
constructors, as the compiler identifies generic class type parameters solely from the class declaration itself and not from the constructors:
class BadCar<T> {
maxSpeed: T;
constructor<T>(): Car<T | undefined>; // error! no type params
constructor<T>(maxSpeed: T): Car<T>; // error! no type params
constructor(maxSpeed?: T) {
this.maxSpeed = maxSpeed; // error! can't tell this is valid
}
}
At present, the workaround involves creating a class that approximates the desired behavior and then annotating or asserting its status as a CarConstructor
:
const Car: CarConstructor = class <T> {
maxSpeed: T;
constructor(maxSpeed?: T) {
this.maxSpeed = maxSpeed as T; // <-- assume undefined is in T
}
}
This solution does work but can be cumbersome, especially as the complexity of the Car
increases (leading to redundant property/method definitions).
An existing GitHub issue, microsoft/TypeScript#35387, addresses this need for easier implementation of classes with overloaded constructors like the ones you desire. If this functionality is crucial for your use case, showing support on that issue by giving it a 👍 and detailing the importance of your scenario could help drive progress.
Alternatively, the simpler approach would be to work harmoniously with TypeScript by considering an alternative solution, such as a factory function:
class DumbCar<T> {
constructor(public maxSpeed: T) { }
}
function newCar<T>(x: T): Car<T>;
function newCar<T>(): Car<T | undefined>;
function newCar<T>(x?: T): Car<T | undefined> | Car<T> {
return new DumbCar(x);
}
Playground link to code