current position:Home>Intensive reading of typescript infer keyword

Intensive reading of typescript infer keyword

2021-08-26 20:10:25 Huang Ziyi

Infer Keyword is used for type derivation in a condition .

Typescript Take it on the official website ReturnType This classic example illustrates its role :

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

Understood as a : If T Inherited extends (...args: any[]) => any type , Then return the type R, Otherwise return to any. among R What is it? ?R Is defined in extends (...args: any[]) => infer R in , namely R Is derived from the incoming parameter type .

intensive reading

We can understand... From two perspectives infer, They are demand perspective and design perspective .

Understand from the perspective of demand infer

Realization infer There must be a demand behind this keyword , This requirement is common Typescript Ability cannot be satisfied .

Imagine a scenario like this : Implement a function , Receive an array , Return to the first item .

We cannot describe this type of derivation with generics , Because generic types are a whole , What we want to return is to enter one of the items , We can't go through things like T[0] Get the first type :

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

In fact, it is reasonable not to support this kind of writing , Because this time is to get the first item type , If T It's an object , We want to go back to it onChange This Key Return value type of , I don't know how to write . Therefore, a new syntax must be used to realize , Namely infer.

From a design perspective infer

In terms of type derivation function , Generics are very powerful , We can use generics to describe the types that are passed in only when called , And describe it in type expressions in advance :

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

But what we found was that T This generic is too holistic , We don't have the ability to Pick Subtype capabilities . That is, for xxx<{label: string}> This scene ,T = {label: string}, But we can't R Defined as {label: R} This position , Because generics are an inseparable whole .

And actually for type safety , Nor can we allow users to describe arbitrary types and locations , In case the type structure passed in is not {label: xxx} It's a callback () => void, Isn't that subtype derivation based on the wrong environment . So considering that you want to get {label: infer R}, First, the parameter must have {label: xxx} Structure , So you can just infer And condition judgment T extends ? A : B To combine , namely :

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

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

That is, if T follow { label: any } Such a structure , Then I can replace any variable position in this structure with infer xxx, If the incoming type satisfies this structure (TS Static analysis link judgment ), You can continue to deduce based on this structure , So in the derivation process, we can use infer xxx Inferred variable type .

Looking back, the first requirement , When you get the first parameter type, you can use infer Realized :

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

It can be understood as , If at this time T Satisfy (...args: any) => any This structure , At the same time, we use infer R Express R This temporary variable refers to the first any Runtime type , Then the return type of the whole function is R. If T Are not satisfied (...args: any) => any This structure , such as GetFirstParamType<number>, Then this derivation is impossible , Go straight back to never The type has a bottom , Of course, you can also customize, such as any Of any kind .


We understand infer After meaning , combining conditional infer This article understands the examples , 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 }>;
 Copy code 

You can see ,ArrayElementType Conditional inference and infer, Represents such a logic : If T A type is an array , And we define each item of the array as E type , Then the return type is E, Otherwise T The overall type itself .

So for item1 It satisfies the structure , So back number, and item2 Does not satisfy the structure , So return its type itself .

In particular , What is returned for the following example ?

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

The answer is number | string, The reason is that we use multiple infer E(infer E)[] amount to [infer E, infer E]... No, multiple variables point to the same type pronoun E Well ) At the same time number and string, So it can be interpreted as E From time to time number From time to time string, So is or relationship , This is covariance .

What if it's 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
 Copy code 

And it turns out that string & number, That is to say . But this example is the same U From time to time string From time to time number ah , Why is the relationship between and , Not or ?

In fact, covariance or inversion is related to infer The parameter position is related to . stay TypeScript in , object 、 class 、 The return value types of arrays and functions are covariant , The parameter type of the function is the inverse relationship , therefore infer If the position is on the function parameter , Will follow the inverse principle .

Contravariant and covariant :

  • Covariance (co-variant): Type convergence .
  • Inversion (contra-variant): Type divergence .

We can open another article on the more in-depth topic of inversion and covariance , I won't go into details here , about infer That's enough to understand .


infer Keywords give us the structure to expand generics in depth , and Pick Type of any of these locations , And used as a temporary variable for the final return type .

about Typescript Type programming , The biggest problem is that you want to achieve an effect but don't know what syntax to use ,infer As a powerful type derivation keyword , It is bound to be useful in most complex type derivation scenarios , So when you encounter difficulties , You can think about whether it can use infer solve the problem .

The address for discussion is : intensive reading 《Typescript infer keyword 》· Issue #346 · dt-fe/weekly

If you want to participate in the discussion , please Click here , New themes every week , Released on weekend or Monday . Front end intensive reading - Help you sift through the right content .

Focus on Front end intensive reading WeChat official account

Copyright notice : Free Reprint - Non commercial - Non derivative - Keep signature ( Creative sharing 3.0 license

copyright notice
author[Huang Ziyi],Please bring the original link to reprint, thank you.

Random recommended