DEV Community

Cover image for Infer in TypeScript, the Great and Powerful
Artem Malko
Artem Malko

Posted on • Edited on

Infer in TypeScript, the Great and Powerful

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?

Confused developer

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;
Enter fullscreen mode Exit fullscreen mode

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 ???;
}
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

What should go next in that statement? We need to check, that T is an array:

type ArrayItemType<T> = T extends [] ? ... : never;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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]
                : {};
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
vallerydelexy profile image
vallerydelexy

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;"

Collapse
 
artemmalko profile image
Artem Malko

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.

you lost me at "TypeScript infers a type quite smoothly — it's a number."

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.

Collapse
 
vallerydelexy profile image
vallerydelexy

yeah, the word infer are like alien to me,
as im not native english speaker.

youre using the word infer to explain infer.

Thread Thread
 
artemmalko profile image
Artem Malko • Edited

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.

let a = 5;
Enter fullscreen mode Exit fullscreen mode

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