This Crazy Syntax Lets You Get An Array Element's Type
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
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
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
Learn how to publish a package to npm with a complete setup including, TypeScript, Prettier, Vitest, GitHub Actions, and versioning with Changesets.
Enums in TypeScript can be confusing, with differences between numeric and string enums causing unexpected behaviors.
Is TypeScript just a linter? No, but yes.
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.