How does the "infer" of Typescript work?

Asked

Viewed 850 times

7

I know it was introduced in language version 2.8 (see here), but even though I read these release notes, I still can’t understand the workings of infer.

  • How it works?
  • What is your real goal?

1 answer

8


The infer is used in conjunction with type conditions in Typescript because it will try infer a type and, if not possible, something must happen. So it must always be in a conditional type.

Basically, infer will "create" a new type, whose name will come before you:

infer NewType ? [[conseguiu-inferir]] : [[não-conseguiu-inferir]]

In which NewType will be the type that is inferred. In this sense, we will be able to use it in the "typical expression" that is positioned in [[conseguiu-inferir]].

An example:

type GetArrayMemberType<T> = T extends Array<infer Member> ? Member : T;

type A = GetArrayMemberType<string[]>; // A inferido para `string`
type B = GetArrayMemberType<string[][]>; // B inferido para `string[]`
type C = GetArrayMemberType<number>; // C inferido para `number`

Link to the playground.

When conditional types are used in conjunction with the infer (which must occupy the first operand of the "ternary conditional"), the type of the second operand (which is usually composed from the inferred type) is returned. If type inference is not possible, the third operand will be returned.

Think about this need for infer need to be associated with a conditional type relating the typescript type system to a mini programming language purely functional. That’s because all kinds of Typescript at all times must be something (as well as an expression in functional programming languages). And what would happen if the guy couldn’t be inferred and we didn’t provide a "clause Else"? The resulting type should be what?

In short, as well as a if haskell’s always need a else, a Typescript conditional type as well at all times needs a else (which is located in the third term of the ternary construction). This, associated with the infer, ensures that we will always have a valid type, even when inference is not possible.

Look at this structure:

//|             Primeiro operando. O `infer` deve ficar nele.
//|                                │
//|                                │           ╔ Nome que será atribuído ao tipo inferido pelo `infer`.
//|                               ┌┴───────────║────┐
//|                               │           ╔╩═══╗│
1 | type UnpackArr<T> = T extends Array<infer Member>
//|    ┌ O segundo operando da condição será retornado caso a inferência tenha sido possível.
//|    │ Geralmente, esse tipo é composto a partir daquele que foi inferido (no caso, `Member`).
//|   ┌┴─────────────┐
2 | ? { type: Member }
//|   ┌ O terceiro operando da condição será retronado caso a inferência não tenha sido possível.
//|   │ Neste caso, só estamos retornando o tipo passado (`T`).
3 | : T;

Note that a name at all times should be placed after the infer. It is the name that will be given to the type that will be inferred. The name can be used in the second operand of the type condition.

What is your real goal?

The goal is to be able to infer types. Most of the time, it is used to "unpack" a type, obtaining the value of an "internal" type".

For example, up to the current Typescript version for when I write this answer, the keyword awaited does not yet exist in Typescript. Therefore, the only way to get the type T of a Promise<T> is from the infer. Behold:

type Awaited<T> = T extends Promise<infer Member> ? Member : T;

type A = Awaited<Promise<string>>; // A inferido `string`.
type B = Awaited<Promise<{ name: string, age: number }>>; // B inferido `{ name: string, age: number }`.

Link to the playground.

In the future, awaited Promise<T> (a construction that does not yet exist in Typescript) will return the type T, but for now the only way to achieve this is by using conditional types in conjunction with infer.

Usually this is more useful for library authors or something that requires a little more complex typing.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.