#
-
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:
- An array of something:
readonly T[]
- A mapper function:
(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
.
#
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.7006
Parameter '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.2345
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.
#
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.