Announcing: A Free Book, A New Course, A Huge Price Cut...
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
You shouldn't use Function
as a type. It represents any function.
Usually, you want to be more specific - like specifying the number of arguments, or what the function returns.
If you do want to represent a function that can take any number of arguments, and return any type, use (...args: any[]) => any
.
Let's imagine you're creating a function which sums up an array of objects. Here's one, taken from the Excalidraw codebase:
const sum = <T >(
array : readonly T [],
mapper : (item : T ) => number
): number =>
array .reduce (
(acc , item ) => acc + mapper (item ),
0
);
Let's look at the type definition. This function takes in:
readonly T[]
(item: T) => number
and returns number
.
In the body, it calls array.reduce(func, 0)
. This means the acc
in the function begins as 0
.
For each member of the array, it then adds acc
and mapper(item)
together. So, you end up with the sum of all of the members of the array.
The mapper
function is the key. Let's strip it out to take a look at it:
type Mapper <T > = (item : T ) => number;
Let's imagine a use case for this:
interface YouTubeVideo {
name : string;
views : number;
}
const youTubeVideos : YouTubeVideo [] = [
{
name : "My favorite cheese",
views : 100,
},
{
name : "My second favorite cheese (you won't believe it)",
views : 67,
},
];
const mapper : Mapper <YouTubeVideo > = (video ) => {
return video .views ;
};
const result = sum (youTubeVideos , mapper ); // 167
Here, mapper
represents the function that extracts the number from the object. The powerful thing about the sum
function is that you can discard most of these type declarations:
const youTubeVideos = [
{ name : "My favorite cheese", views : 100 },
{
name : "My second favorite cheese (you won't believe it)",
views : 67,
},
];
const result = sum (youTubeVideos , (video ) => {
return video .views ;
}); // 167
We've actually discarded all of the type declarations, but video
is still inferred as { name: string; views: number }
. This is possible because of the specificity of our function definition: (item: T) => number
.
Function
?The big mistake I see a lot of beginner devs making is declaring a function like mapper
with the Function
type:
const sum = <T >(
array : readonly T [],
mapper : Function
): number =>
array .reduce (
(acc , item ) => acc + mapper (item ),
0
);
This keyword basically stands for 'any function'. It means that sum
can technically receive any function.
When used in sum
, we lose a lot of the safety that (item: T) => number
provided:
const result = sum (youTubeVideos , (item ) => {Parameter 'item' implicitly has an 'any' type.7006Parameter 'item' implicitly has an 'any' type. // We can return anything from here, not just
// a number!
return item .name ;
});
TypeScript now can't infer what item
is supposed to be, or what our mapper function is supposed to return.
The lesson here is 'don't use Function
' - there's always a more specific option available.
Sometimes, you'll want to express 'any function' in TypeScript. For this, let's look at some of TypeScript's built-in types, Parameters
and ReturnType
.
export type Parameters <
T extends (...args : any) => any
> = T extends (...args : infer P ) => any
? P
: never;
export type ReturnType <
T extends (...args : any) => any
> = T extends (...args : any) => infer R ? R : any;
You'll notice that both of these utility types use the same constraint: (...args: any) => any
.
(...args: any)
specifies that the function can take any number of arguments, and => any
indicates that it can return anything.
For expressing a function with no arguments (but that returns anything), you'll want to use () => any
:
const wrapFuncWithNoArgs = (func : () => any) => {
try {
return func ();
} catch (e ) {}
};
wrapFuncWithNoArgs (( a : string ) => {} );Argument of type '(a: string) => void' is not assignable to parameter of type '() => any'.
Target signature provides too few arguments. Expected 1 or more, but got 0.2345Argument of type '(a: string) => void' is not assignable to parameter of type '() => any'.
Target signature provides too few arguments. Expected 1 or more, but got 0.
Function
should never be used when expressing types.
(a: string, b: number) => any
syntax can be used when you want to specify only the arguments, but not the return type.
(...args: any) => any
can be used to represent any function type.
Share this article with your friends
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.
Learn how to use corepack
to configure package managers in Node.js projects, ensuring you always use the correct one.
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.