- Inferring Object and Function Types in TypeScript
- Getting the type of an object
- Getting the return type of a function
- Getting the return type of an asynchronous function
- Typing Functions in TypeScript
- #Method Signatures
- #Function Type Literals
- #Object Type Literals with Call or Construct Signatures
- Advanced TypeScript Fundamentals
Inferring Object and Function Types in TypeScript
TypeScript’s powerful inference helps us avoid bloating our code with lots of type annotations. The typeof keyword can help us when we want to strongly-type a variable from another variable’s type.
Let’s go through an example where this is useful in React.
Getting the type of an object
Here’s a snippet of a strongly-typed form in React:
const defaultState = name: "", email: "" >; const App = () => const [values, setValues] = React.useState(defaultState); const handleSubmit = (e: React.FormEventHTMLFormElement>) => e.preventDefault(); save(values); >; . >;
The values state is inferred to be of type < name: string, email: string >from the defaultState variable.
In handleSubmit , we call a save function outside the component which will be something like:
const save = (data) => // post the data to the server . >;
We are in TypeScript land, so we need to give the data parameter a type annotation. Does this mean we now have to create a type for this? Fortunately, we can use the inferred type of defaultState using the typeof keyword:
const save = (data: typeof defaultState) => // post the data to the server . >;
Getting the return type of a function
Sometimes it’s useful to get the return type of a function.
An example is in Redux code, where we want to create a union type for all the actions for use on the action reducer function parameter. So, if we have the following action creators:
function addPerson(personName: string) return type: "AddPerson", payload: personName, > as const; > function removePerson(id: number) return type: "RemovePerson", payload: id, > as const; >
… we want to create a union type as follows:
type Actions = | return type of addPerson | return type of removePerson
This isn’t valid syntax, but hopefully you get the idea of what we are trying to do.
Luckily there is handy utility type called ReturnType that we can use:
type Actions = | ReturnTypetypeof addPerson> | ReturnTypetypeof removePerson>;
This gives us exactly the type that we require:
readonly type: "AddPerson"; readonly payload: string; > | readonly type: "RemovePerson"; readonly payload: number; >
Getting the return type of an asynchronous function
What if the functions are asynchronous?
async function addPersonAsync( personName: string ) await wait(200); return type: "AddPerson", payload: personName, > as const; > async function removePersonAsync(id: number) await wait(200); return type: "RemovePerson", payload: id, > as const; > type ActionsAsync = | ReturnTypetypeof addPersonAsync> | ReturnTypetypeof removePersonAsync>;
The ActionsAsync type isn’t quite what we require:
Promise readonly type: "AddPerson"; readonly payload: string; >> | Promise readonly type: "RemovePerson"; readonly payload: number; >>
What we want is the type after the promise is resolved.
Let’s look at the definition of ReturnType :
type ReturnType T extends (. args: any) => any > = T extends (. args: any) => infer R ? R : any;
This looks a bit tricky, so, let’s break it down:
- The type is a generic type where the parameter has the signature T extends (. args: any) => any . i.e. we need to pass a function type or a type error will occur.
- The type is a conditional type with the condition being whether the parameter has a function signature.
- infer R is the valuable bit in the condition because this puts the return type from the generic parameter into a R parameter.
- If the condition is true (i.e. the generic parameter is a function) then R is returned (i.e. the function’s return type).
- any is returned if the condition is false .
This explains why we are getting the Promise included in our asynchronous function return type.
Let’s have a go at creating our own utility type for the return type of an asynchronous function:
type ReturnTypeAsync T extends (. args: any) => any > = T extends (. args: any) => Promiseinfer R> ? R : any;
So our Actions union type becomes:
type ActionsAsync = | ReturnTypeAsynctypeof addPersonAsync> | ReturnTypeAsynctypeof removePersonAsync>;
This gives the type we expect:
readonly type: "AddPerson"; readonly payload: string; > | readonly type: "RemovePerson"; readonly payload: number; >
If we want to be clever and improve ReturnTypeAsync so that it works with asynchronous functions as well as synchronous ones, we can do it as follows:
type ReturnTypeAsync T extends (. args: any) => any > = T extends (. args: any) => Promiseinfer R> ? R : T extends (. args: any) => infer R ? R : any;
If you to learn more about using TypeScript with React, you may find my course useful:
Typing Functions in TypeScript
In TypeScript, there are multiple syntaxes for declaring the type of a function:
Here’s a quick run-down of all three variants.
#Method Signatures
The method signature syntax is probably the most straightforward to use. When defining an object type, its methods can easily be described by providing signatures as follows:
interface Date toString(): string; setTime(time: number): number; // . >
Note how the method signature syntax closely mirrors the shorthand method syntax, which is used to concisely define methods in object literals or ES2015 classes:
class Date // . toString(): string // . > setTime(time: number): number // . > >
#Function Type Literals
Function type literals are another way to declare the type of a function. They’re typically used in the signature of a higher-order function, that is, a function which accepts functions as parameters or which returns a function:
interface ArrayT> sort(compareFn?: (a: T, b: T) => number): this; // . >
Perhaps surprisingly, the parameter names are always required within a function type literal. You can’t leave out the parameter name and only specify the type. Here’s how TypeScript sees a function type literal if you leave out the colon:
type FunctionType1 = (x: string, y: number) => number; // (x: string, y: number) => number type FunctionType2 = (string, number) => number; // (string: any, number: any) => number
In the definition of the FunctionType2 type, string and number aren’t interpreted as types, but as parameter names. They are implicitly typed as any because there’s no explicit type annotation (and no information for contextual typing).
#Object Type Literals with Call or Construct Signatures
In JavaScript, functions are nothing but special objects than can be called. This fact is reflected in the syntax of object type literals: they describe the shape of an object, which also happens to have a call signature:
interface RegExpConstructor // Call signatures (pattern: RegExp): RegExp; (pattern: string, flags?: string): RegExp; // . >
Similar to call signatures, an object type literal can also contain construct signatures, in which case it is said to be a constructor type. The construct signature of a function defines its parameter list and return type when it’s called with the new operator. Construct signatures look almost identical to call signatures, except that they are additionally prefixed with the new keyword:
interface RegExpConstructor // Call signatures (pattern: RegExp): RegExp; (pattern: string, flags?: string): RegExp; // Construct signatures new (pattern: RegExp): RegExp; new (pattern: string, flags?: string): RegExp; // . >
Depending on how the RegExp type of the JavaScript standard library is used, either the call or construct signature applies. In this case, they’re identical, but they wouldn’t have to be:
// Using the call signature const digitsPattern1 = RegExp("^\\d+$"); // Using the construct signature const digitsPattern2 = new RegExp("^\\d+$");
Advanced TypeScript Fundamentals
A deep dive into the fundamnetals of TypeScript’s type system. Learn about the optional chaining ( ?. ) and nullish coalescing ( ?? ) operators, assertion functions, truly private class fields, conditional types, template literal types, adn more.