Reputation: 51
How can I provide return type hinting from a function parameter?
class BaseClass {
constructor(private logger:Logger){}
}
class ChildClass extends BaseClass {
public helloWorld() {
console.log('Hello World')
}
}
function buildInstance<T extends BaseClass>(target: typeof BaseClass, logger: Logger): T {
logger.defaults.meta = { path: __dirname }
return new target(logger) as T
}
const instance = buildInstance<ChildClass>(ChildClass)
instance.helloWorld() // instance is understood to be ChildClass instance
The above works, in so far as, instance
is type hinted for ChildClass correctly, but I feel like that is messy and I might be missing something. Is there someway to simply my function / call signatures to infer return type from the argument. Or conversely, is there a way to infer new target
from the type argument?
Upvotes: 0
Views: 709
Reputation: 524
If you declare your function as buildInstance<T>(type: T): T
, you're telling TypeScript that type
is an instance of T
(the same type that is returned, the instance type). But you want to pass a class into it, hence you need to express that. buildInstance<T>(type: Class<T>): T
satisfies this.
Demo: https://repl.it/@chvolkmann/DimgrayGiddyVertex
// something that can be constructed with "new", returning T
type Class<T> = new (...args: any[]) => T
class Vehicle {}
class Car extends Vehicle {}
class Truck extends Vehicle {}
function buildVehicle<T extends Vehicle>(Type: Class<T>) : T {
return new Type()
}
const car = buildVehicle(Car) // type: Car
const truck = buildVehicle(Truck) // type: Truck
Here is the same example, but slightly more involved. Here, also the arguments you can pass to the respective class are type checked. Might require TypeScript 4+ for all that generic jazz.
// something that can be constructed with "new", accepting argument of type A, returning T
type Class<T, A extends any[] = any[]> = new (...args: A) => T
class Vehicle {
constructor(x: number) {}
}
class Car extends Vehicle {}
class Truck extends Vehicle {}
function buildVehicle<T extends Vehicle, A extends any[]>(Type: Class<T, A>, ...args: A) : T {
// args is type checked!
// A is a tuple of whatever Vehicle expects as constructor argument types
return new Type(...args)
}
// compiles fine
let car = buildVehicle(Truck, 42)
// TS2554: Expected 2 arguments, but got 1.
car = buildVehicle(Car)
// TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
car = buildVehicle(Truck, 'foobar')
Upvotes: 2