5

The Infer keyword is used for type inference in conditions.

Typescript official website also took ReturnType illustrate its role:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

Understood as follows: If T inherited extends (...args: any[]) => any type, type of return R , otherwise any . What is R R is defined in extends (...args: any[]) => infer R , that is, R is derived from the input parameter type.

intensive reading

We can understand infer from two perspectives, namely the demand perspective and the design perspective.

Understanding infer from a demand perspective

The realization of infer must be a requirement behind it, which cannot be met by ordinary Typescript capabilities.

Imagine such a scenario: implement a function, receive an array, and return the first item.

We cannot use generics to describe this type of derivation, because generic types are a whole, and what we want to return is one of the T[0] parameters. We cannot get the first type by writing like 06125c0538a03e:

function xxx<T>(...args: T[]): T[0]

In fact, it is reasonable not to support this writing method, because this time is to get the first item type. If T is an object, we want to return the return value type of the Key onChange Therefore, a new syntax must be used at this time, which is infer .

Understanding infer from a design perspective

From the point of view of the type inference function, the generic function is very powerful. We can use the generic to describe the type passed in when calling, and describe it in the type expression in advance:

function xxx<T>(value: T): { result: T }

But we found that T too integrated, and we do not yet have the ability to pick subtypes from it. That it is, for xxx<{label: string}> this scenario, T = {label: string} , but we can not R defined as {label: R} this position, because generics are a whole can not be split.

And in fact, for the sake of type safety, we cannot allow users to describe any type position. If the incoming type structure is not {label: xxx} but a callback () => void , isn't the subtype derivation built in the wrong environment? So considering that you want to get {label: infer R} , first, the parameters must have {label: xxx} , so it is just right to combine infer with conditional judgment T extends ? A : B , namely:

type GetLabelTypeFromObject<T> = T extends ? { label: infer R } ? R : never

type Result = GetLabelTypeFromObject<{ label: string }>;
// type Result = string

That is, if T follows { label: any } , then I can replace any variable position in this structure with infer xxx . If the incoming type meets this structure (judging by the TS static analysis link), I can continue to derive based on this structure, so in the derivation In the process, we can use the variable type inferred by infer xxx

Looking back at the first requirement, you can use infer achieve the first parameter type:

type GetFirstParamType<T> = T extends ? (...args: infer R) => any ? R[0] : never

It can be understood that if T satisfies (...args: any) => any this time, and we use infer R indicate that R refers to the first any runtime type, then the type returned by the entire function is R . If T not met (...args: any) => any this structure, such as GetFirstParamType<number> , simply out of the question that this is derived directly back never type reveal all the details, of course, you can also customize such any any type of class.

Overview

After we understand the infer , we combine conditional infer understand the examples in this article, which will help deepen memory.

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

As you can see, ArrayElementType uses conditional inference and infer to express such a logic: if the T type is an array, and we define each item of the array as E type, then the return type is E , otherwise it is the overall type T itself.

So for item1 is to meet the structure, so it returns number , and item2 does not meet the structure, so the type itself is returned.

In particular, what does the following example return?

type item3 = ArrayElementType<[number, string]>;

The answer is number | string . The reason is that we use multiple infer E ( (infer E)[] equivalent to [infer E, infer E]... is that multiple variables point to the same type pronoun E number and string at the same time, so it can be understood as E when number is string , so it can be understood as 06125c0538a350 Or relationship, this is covariance.

What if it is a function parameter?

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U : never
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number

It is found that the result is string & number , which is inverted. But this example is the same U sometimes as string sometimes as number Yeah, why is the relationship and, instead, or do?

In fact, covariance or contravariance is related to the position of the infer In TypeScript, the return value types of objects, classes, arrays, and functions are all covariant, and the parameter types of functions are contravariant relationships, so infer position of 06125c0538a3c6 is on the function parameter, the contravariant principle will be followed.

Inversion and covariation:

  • Covariant (co-variant): Type convergence.
  • Contra-variant: Type divergence.

You can open another article on the in-depth topic of inverter and coordination change, and I won’t go into infer here. It’s enough to understand 06125c0538a479.

Summarize

infer keyword gives us the ability to expand the generic structure in depth, pick any type in it, and use it as a temporary variable for the final return type.

For Typescript type programming, the biggest problem is that you want to achieve an effect but you don’t know what syntax to use. infer will inevitably come in handy in most complex type deduction scenarios, so you encounter difficulties At the time, you can think about whether you can use infer solve the problem.

The discussion address is: Intensive Reading of "Typescript infer Keywords" · Issue #346 · dt-fe/weekly

If you want to participate in the discussion, please click here , a new theme every week, weekend or Monday. Front-end intensive reading-to help you filter reliable content.

Follow front-end intensive reading WeChat public

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

Copyright statement: Freely reprinted-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )

黄子毅
7k 声望9.5k 粉丝