Add TypeScript To An Existing React Project
Learn how to add TypeScript to your existing React project in a few simple steps.
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:
ts
constsum = <T >(array : readonlyT [],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:
ts
typeMapper <T > = (item :T ) => number;
Let's imagine a use case for this:
ts
interfaceYouTubeVideo {name : string;views : number;}constyouTubeVideos :YouTubeVideo [] = [{name : "My favorite cheese",views : 100,},{name : "My second favorite cheese (you won't believe it)",views : 67,},];constmapper :Mapper <YouTubeVideo > = (video ) => {returnvideo .views ;};constresult =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:
ts
constyouTubeVideos = [{name : "My favorite cheese",views : 100 },{name : "My second favorite cheese (you won't believe it)",views : 67,},];constresult =sum (youTubeVideos , (video ) => {returnvideo .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:
ts
constsum = <T >(array : readonlyT [],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:
ts
constParameter 'item' implicitly has an 'any' type.7006Parameter 'item' implicitly has an 'any' type.result =sum (youTubeVideos , () => { item // We can return anything from here, not just// a number!returnitem .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
.
ts
export typeParameters <T extends (...args : any) => any> =T extends (...args : inferP ) => any?P : never;export typeReturnType <T extends (...args : any) => any> =T extends (...args : any) => inferR ?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
:
ts
constwrapFuncWithNoArgs = (func : () => any) => {try {returnfunc ();} catch (e ) {}};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.wrapFuncWithNoArgs ((a : string) => {});
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 add TypeScript to your existing React project in a few simple steps.
Learn the essential TypeScript configuration options and create a concise tsconfig.json file for your projects with this helpful cheatsheet.
Big projects like Svelte and Drizzle are not abandoning TypeScript, despite some recent claims.
Learn different ways to pass a component as a prop in React: passing JSX, using React.ComponentType, and using React.ElementType.
Learn about TypeScript performance and how it affects code type-checking speed, autocomplete, and build times in your editor.
When typing React props in a TypeScript app, using interfaces is recommended, especially when dealing with complex intersections of props.