type of function's parameters list; type of arguments list; tuples;
There are functions a()
and b()
. Function a()
have the same signature (parameters) as function b()
.
Instead of manually copying list of parameters with their types from b()
to a()
, there should be a way to assign types of parameters to a()
from b()
.
function a(...args: /* possible syntax starts */ parametersof b /*possible syntax ends*/ ) {
b(...args)
}
function b (a: string, b: number) { /* ... */}
a('foo', 1) // ok
a('foo', 'bar') // compiler error "Argument of type 'bar' is not assignable to parameter of type 'number'"
parametersof ...
should probably return a tuple.
Related question on Stackoverflow
function a(...args: parametersof b) {
// ... do some stuff
b(...args)
}
import someFunc from './someFunc'
export const someAPI = {
someFunc, // normal one, we just re-exporting this func
async lazyFunc(...args: parametersof eagerFunc) {
const {eagerFunc} = await import('./eagerFunc')
return eagerFunc(...args)
}, // lazy func. helps to reduce main bundle
get node() {
if (runningInNode()) {
return {
async nodeFunc(...args: parametersof nodeFunc) {
const {nodeFunc} = await import('./nodeFunc')
return nodeFunc(...args)
}
}
}
} // here we do not add code, that is supposed to work only in node.js, to frontend bundle)
}
function a(a: string, b: number) {
b(...args)
}
function b (a: string, b: number) {/*...*/}
Above practice have these disadvantages:
1) When changing b
's signature, we also have to change a
's one. That is little bit harder maintenance and a window for new errors.
2) Contradicts with DRY principle
I am aware of new "tuples" feature in TS 3.0, which is great! However using tuples for this use case requires creating new types, while "parametersof" solution will end up in minimum possible amount of code and maximum readability.
My suggestion meets these guidelines:
Somewhere, @ahejlsberg proposed adding the following definition to lib.d.ts:
type Parameters<T> = T extends (... args: infer T) => any ? T : never;
I think this is the right way to go here over a new operator.
If it's useful, here's ReturnType as well:
type ReturnType<T> = T extends (... args: any[]) => infer T ? T : never;
@bterlson thank you for quick response!
My thoughts on the solutions provided by you:
T
(I guess a function?). P.S. And yes! I forgot about return types, they are also should have some way to be assigned from one func to another.
@anurbol if it gets added to lib.d.ts you can just use Parameters<some function type>
without understanding how it works (much like how Pick
or Record
work today).
Probably the best resource to understand this stuff is the 2.8 release blog. Parameters
and ReturnType
are conditional types using the infer
keyword in the conditional to tell TS to infer T
's argument list or return type respectively.
Also, here's an example of using Parameters
:
function foo(a: number, b: string) {
return true;
}
type p = Parameters<typeof foo>;
// p = [number, string]
Thank you, @bterlson! I love TS even more now! :heart: And ReturnType (BTW one does not have to add it to lib.d.tsm it is already included in the core lib), fantastic!
@bterlson using TS 3.0.1, it seems like the definition above for Parameters
just returns an empty Object type. Ex:
type Parameters<T> = T extends (... args: infer T) => any ? T : never;
const x: test = (a: number) => 5;
type Params = Parameters<typeof x>;
const a: Params = ['should-not-work']; // works, but shouldn't
function t(...args: Params) {} // does not work, but should work
Yields
[ts] A rest parameter must be of an array type.
type Params = {}
Any ideas on what might be wrong? Or is re-using function argument types for new functions not yet supported?
EDIT: Nevermind, this does work in 3.0. I had a dependency in my test repo that was pulling in the wrong version.
is there any possibility to extract parameter names as well? e.g.
function foo(a: number, b: string) {
return true;
}
type p = ParametersWithNames<typeof foo>;
// p = { a: number, b: string }
edit: probably I want too much and parameter names don't relate to type system..
@pleerock There is no way to extract the parameter names to an object type.
Parameter names will however be preserved if you extract form one function and spread to another function:
function foo(x: number, y: number) {
}
//function bar(x: number, y: number): void
function bar(...a: Parameters<typeof foo>) {
}
Thanks @dragomirtitian. That's interesting behavior. However I wanted to create an object type out of parameter names, e.g. { x: number, y: number }
What if you only have one parameter and want that parameter, not an array with that parameter?
function foo(a: number) {
return true;
}
type p = Parameters<typeof foo>;
// p = number
@dianjuar
function foo(a: number) {
return true;
}
type p = Parameters<typeof foo>[0];
In a similar vein to this, is it possible to first define the args expected and then use them in both functions? For example...
type CommonArgs = {
a: number;
b: number;
}
function foo(...a: CommonArgs) {
}
function bar(...a: CommonArgs) {
}
It doesn't really make sense in the above snippet alone but what I am interested in is reducing boilerplate when it comes to function overloading. If we want the return type to be narrowed based on an input
// foo can return two different types based upon an input, say a boolean
function foo(a: number, b?: boolean): string | number {
if (b === false) {
return a; // return type is number
}
return '' + a; // return type is string
}
const value = foo(10, false); // <= typeof value: string | number
Math.abs(value); // Argument of type 'string | number' is not assignable to parameter of type 'number'
// this can be solved with
function foo(a: number, b: true): string;
function foo(a: number, b: false): number;
function foo(a: number, b?: boolean) {
if (b === false) {
return a;
}
return '' + a;
}
const value = foo(10, false); // correctly infers return type as number
This works but is only a trivial number of args, so could be more boilerplate to copy and paste. Is it possible for something like...
type CommonArgs = {
a: number;
// c, d, e, etc...
}
function foo(...a: CommonArgs & { b: true }): string
function foo(...a: CommonArgs & { b: false }): number
function foo(a: number, b?: boolean) {
if (b === false) {
return a;
}
return '' + a;
}
I have attempted using conditional types but have not managed to do so far...
EDIT
Additionally it would be nice to add some type-safety to the function implementation, along the lines of:
type CommonArgs = {
a: number;
b?: boolean;
// c, d, e, etc...
}
type CommonReturnType = string | number;
type CommonFunction = (...a: CommonArgs): CommonReturnType;
function foo(...a: CommonArgs & { b: true }): string
function foo(...a: CommonArgs & { b: false }): number
foo: CommonFunction = function(a: number, b?: boolean) {
return {}; // compiler identifies this return type is not allowed
}
@bhishp
I think the parameters only exist in value space
: They are not keys(string | number) of an object but variables.
Even you get a type like [arg0: string, arg1, number]
by writing Parameters<some-function-type>
,
the parameter names are lost in the type space
.
Most helpful comment
Somewhere, @ahejlsberg proposed adding the following definition to lib.d.ts:
I think this is the right way to go here over a new operator.
If it's useful, here's ReturnType as well: