Learn TypeScript
Inferring types in a conditional type
Inferring types in a conditional type
In this lesson, we will learn how we can extract and use types within a condition type.
Understanding the infer
keyword
There is an infer
keyword that can be used within a condition in a conditional type to put the inferred type into a variable. That inferred variable can then be used within the conditional branches.
Consider the simple example below which gives the array element type if the type is an array:
type ArrayElementType<T> = T extends (infer E)[] ? E : T;// type of item1 is `number`type item1 = ArrayElementType<number[]>;// type of item1 is `{name: string}`type item2 = ArrayElementType<{ name: string }>;
When item1
is constructed, the condition in the conditional type is true
because number[]
matches (infer E)[]
. E
is therefore inferred to be number
during this matching process. The first branch of the condition, E
, is returned, which is resolved to be number
.
When item2
is constructed, the condition in the conditional type is false
because {name: string}
does not match (infer E)[]
. Therefore the second branch of the condition, T
, is returned, which is the original parameter passed in, {name: string}
.
An example
We are going to create a conditional type making use of the infer
keyword in the code editor below:
The code contains two functions that return different objects. The code also includes types for the two functions.
We will create a utility type called FunctionReturnType
that will give the return type of a function. We will eventually create a union type consisting of the return types of the two functions using FunctionReturnType
.
- Let's start to create a type alias for
FunctionReturnType
:
type FunctionReturnType<T>
- Add the condition for
FunctionReturnType
:
type FunctionReturnType<T> = T extends (...args: any) => infer R
Our condition is that the type passed in matches a function signature. We have inferred the return type of the function to be put in a R
parameter.
- Add the branches of logic to the conditional type:
type FunctionReturnType<T> = T extends (...args: any) => infer R ? R : T;
The R
parameter is returned if the condition is true
, which is the function's return type. The original type passed in, T
, is returned if the condition is false
.
- Create a union type called
Actions
that consists of the return types of theaddPerson
andremovePerson
functions usingFunctionReturnType
.
Actions
is equivalent to the type below:
{ type: string; payload: string;} | { type: string; payload: number;}
Great!
- Now try
FunctionReturnType
by passing in an object:
const person = { name: "Fred" };type PersonType = FunctionReturnType<typeof person>;
What is the type of PersonType
?.
It's nice that FunctionReturnType
can handle non-function types correctly. It would be nicer if a type error was raised if we tried to pass in a non-function type.
How can we add a constraint to FunctionReturnType
so that only functions can be passed to it?.
A type error is now raised on PersonType
, which is great.
We have actually just created a type that already exists as a standard utility type called ReturnType
. The definition of ReturnType
is below:
type ReturnType<T extends (...args: any) => any> = T extends ( ...args: any) => infer R ? R : any;
This is similar to our implementation. A difference is in the second branch of the condition (T
v any
), which should never be reached because of the generic parameter constraint.
Summary
The infer
keyword allows types to be extracted from conditions in conditional types. This is often used within Typescript's standard utility types.
In the next lesson, we will gain a deep understanding of some of TypeScripts standard conditional utility types.