Get argument types for function / class constructor
TypescriptTypesConstructorTypescript Problem Overview
I am trying to do something I am not sure is possible in TypeScript: inferring the argument types/return types from a function.
For example:
function foo(a: string, b: number) {
return `${a}, ${b}`;
}
type typeA = <insert magic here> foo; // Somehow, typeA should be string;
type typeB = <insert magic here> foo; // Somehow, typeB should be number;
My use case is to try to create a config object that contains constructors and parameters.
For example:
interface IConfigObject<T> {
// Need a way to compute type U based off of T.
TypeConstructor: new(a: U): T;
constructorOptions: U;
}
// In an ideal world, could infer all of this from TypeConstructor
class fizz {
constructor(a: number) {}
}
const configObj : IConfigObj = {
TypeConstructor: fizz;
constructorOptions: 13; // This should be fine
}
const configObj2 : IConfigObj = {
TypeConstructor: fizz;
constructorOptions: 'buzz'; // Should be a type error, since fizz takes in a number
}
Typescript Solutions
Solution 1 - Typescript
Typescript now has the ConstructorParameters
builtin, similar to the Parameters
builtin. Make sure you pass the class type, not the instance:
ConstructorParameters<typeof SomeClass>
Solution 2 - Typescript
With TypeScript 2.8 you can use the new extends
keyword:
type FirstArgument<T> = T extends (arg1: infer U, ...args: any[]) => any ? U : any;
type SecondArgument<T> = T extends (arg1: any, arg2: infer U, ...args: any[]) => any ? U : any;
let arg1: FirstArgument<typeof foo>; // string;
let arg2: SecondArgument<typeof foo>; // number;
let ret: ReturnType<typeof foo>; // string;
Solution 3 - Typescript
Typescript 2.8 added conditional types with type inference
Typescript 3.0 added rest-elements-in-tuple-types, so you can get all the arguments in an Array
type now.
type ArgumentsType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any ? A : never;
type Func = (a: number, b: string) => boolean;
type Args = ArgumentsType<Func> // type Args = [number, string];
type Ret = ReturnType<Func> // type Ret = boolean;
You can use it like this:
const func = (...args: Args): Ret => { // type the rest parameters and return type
const [a, b] = args; // spread the arguments into their names
console.log(a, b); // use the arguments like normal
return true;
};
// Above is equivalent to:
const func: Func = (a, b) => {
console.log(a, b);
return true;
}
Solution 4 - Typescript
I'll throw in a more direct answer for the use case of extracting the constructor argument types.
type GetConstructorArgs<T> = T extends new (...args: infer U) => any ? U : never
class Foo {
constructor(foo: string, bar: number){
//
}
}
type FooConstructorArgs = GetConstructorArgs<typeof Foo> // [string, number]
Solution 5 - Typescript
How about this approach:
interface IConfigObject<T, U> {
TypeConstructor: new(a: U) => T;
constructorOptions: U;
}
class fizz {
constructor(a: number) {}
}
function createConfig<U, T>(cls: { new (arg: U): T }, arg: U): IConfigObject<T, U> {
return {
TypeConstructor: cls,
constructorOptions: arg
}
}
const configObj = createConfig(fizz, 3); // ok
const configObj2 = createConfig(fizz, "str"); // error
Edit
You can have an indexed type variable:
const configs: { [name: string]: IConfigObject<any, any> } = {
config1: createConfig(fizz, 3),
config2: createConfig(fizz, "str"), // error
config3: createConfig(buzz, "str")
}