Let's talk about the infer
keyword in TypeScript. I believe, there're a lot of developers, who've been confused about infer
. When should I use it? How it works? What is the purpose?
Let's start with something easier. The next cases will help us to understand the concept of infer
. Ok, look at the piece of code:
let a = 10;
TypeScript knows a type of a
— it's a number. This fact allows developers to not write such types everywhere. TypeScript takes care of it. By the way, where will be another type in case of const
usage. Try it by yourself. I'm waiting for your explanation in the comments. This is a small interactive with a reader)
Ok, we've just found out, TypeScript is pretty smart. If a value corresponds to a specific condition, TS can make a decision about its type. Something like this:
if (typeof a === 'number') {
return number;
} else {
return ???;
}
What does ???
mean?) Meet the never
type. Looks quite useless, who'd like to use it? In TypeScript, never
is treated as the no value type. Do we have any functions, which do not return a value (undefined is a correct value)? By the way, we can create such function. For example:
function foo() {
throw new Error('error');
}
Check out the return type of foo
.
Actually, the never
type is not so useless. Let's come back to that example with number. We can use never
in the else condition:
if (typeof a === 'number') {
return number;
} else {
return never;
}
// or
typeof a === 'number' ? number : never;
Of course, where is no such code in TypeScript's codebase. It's just a model, how TypeScript works.
And there is an equivalent for if/else in TypeScript. I'm talking about the extends
keyword:
type NumberFromType<T> = T extends number
? number
: never;
Hm, there is something new — <T>
. Let's think about it like it is a box for a type. T
can be any type. We will put the type into that box as soon as it will be defined anywhere in your statement. The classic example:
function test<T>(x: T): T {
return x;
}
test(10);
If you call it like test(10);
, TypeScript will defined T
as number
, cause x
was number
. This is quite simple explanation, I know. But it's enough right now) Ok, we can moving on.
Let's come back to NumberFromType
. T extends number
means that it’s safe to assume that a value of type T
is also of type number
. For example, 10 extends number
because let a: number = 10
is type-safe. Hm, what happens, if we pass a string into NumberFromType<T>
? :
type A = NumberFromType<'10'>;
// It will be treated like this:
type NumberFromType<'10'> = '10' extends number ? number : never;
As a result, there will be never
in the type A
, cause string is not extended from number. But what if we need to work with strings too? We can put one condition into another. Something like this:
type StringOrNumberFromType<T> = T extends string
? string
: T extends number
? number
: never
And voila, it works for strings and numbers) So, you can go deeper and put more conditions there, for each of type.
The infer keyword
Finally we are ready for infer
! First of, the infer
keyword can be used in conditional types only. And there is a really simple explanation — you can not use it anywhere else =) This keyword doesn't have any meanings outside of a conditional type.
Ok, let's try to pass an array of numbers into NumberFromType
. The result is never
, obviously. An array of numbers is not extended from number. But what if we need to get a type of an array item? It can be something like this:
type ArrayItemType<T> = T extends ...
What should go next in that statement? We need to check, that T
is an array:
type ArrayItemType<T> = T extends [] ? ... : never;
I've just finished the condition, like it was in NumberFromType
. But the main question is not resolved, cause we need a type of the array's item. By the way, we can write a type for array of numbers like number[]
or Array<number>
. Any other type can be inside <>
braces. So, our condition can be written in a such way:
type ArrayItemType<T> = T extends Array<ITEM_TYPE>
? ITEM_TYPE
: never;
Ok, it's much better! But ITEM_TYPE
is not defined. It has not been inferred yet. Yes, we need TypeScript to infer the type. We can ask TypeScript to do it:
type ArrayItemType<T> = T extends Array<infer ITEM_TYPE>
? ITEM_TYPE
: never;
It means, if T
is extended from an Array
type, so, TypeScript, it would be really kind of you, if you will infer the type of T's
item and will return it as a result.
In general, we can say, that the infer
keyword and conditional typing in TypeScript allows us to take a type and isolate any piece of it for a later usage.
There are some real life examples.
Unpromisify
export type Unpromisify<T> = T extends Promise<infer Result>
? Result
: never;
As it follows from its name, Unpromisify<T>
returns a Promise's result. By the way, if you use TypeScript 4.5 or higher, you can use the built in type Awaited
. There are some examples with Awaited
on typescriptlang.org.
ComponentProps
In React, we often need to access prop types of a component. To do that, React offers a utility type for accessing prop types powered by the infer
keyword called ComponentProps
. You can find the full definition in DefinitelyTyped repository.
type ComponentProps<
T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> =
T extends JSXElementConstructor<infer P>
? P
: T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: {};
After checking that T
is a React component (T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
), it infers its props and returns them (T extends JSXElementConstructor<infer P> ? P
). And the tail is about a simple React Element.
Conclusion
The infer
keyword is a powerful tool that allows us to unwrap and store types from any complex type. It is like a type unboxing. So, there is no any mystery behind this keyword.
If you want challenge your TypeScript skills, I'd like to recommend you type-challenges. And I'm pretty sure, this post will be really useful. Enjoy)
Top comments (6)
your writing is hard to digest.
you lost me at "TypeScript infers a type quite smoothly — it's a number."
which doesnt seem to explain what is infer nor what it has to do with "let a = 10;"
I'll try to fix my style in the future, thx for the feedback.
The main idea was to tell you about
infer
by using some simple concepts.Please, correct me if I'm wrong, the problem is, that I used a word
infers
here?) By the way, try to read some more. I'm pretty sure, you'll get the idea.yeah, the word infer are like alien to me,
as im not native english speaker.
youre using the word infer to explain infer.
Yes, it can confuse you)
There are two ideas behind this word: exact mean — to make a decision about something, based on some facts and the keyword
infer
.This is the most simple example of the infer process. This example is used to explain the concept of inferring in TypeScript.
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more