introduce

Infer, which first appears in this PR, represents type variables to be inferred in the extends condition statement.

A simple example is as follows:

type ParamType<T> = T extends (param: infer P) => any ? P : T;
Copy the code

The conditional statement T extends (param: infer P) => any? In P: T, infer P means function parameters to be inferred.

Infer P => ANY If T can be assigned to (Param: infer P) => any, the result is (Param: infer P) => any, otherwise T is returned.

interface User {
  name: string;
  age: number;
}

type Func = (user: User) = > void

type Param = ParamType<Func>;   // Param = User
type AA = ParamType<string>;    // string
Copy the code

Built-in types

In version 2.8, TypeScript includes some mapping types related to infer:

  • The return value type used to extract function types:

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

    Compared with the example given at the beginning of this article, ReturnType

    only moves infer P from the parameter position to the return value position, so P represents the return value type to be inferred.

    type Func = (a)= > User;
    type Test = ReturnType<Func>;   // Test = User
    Copy the code
  • Used to extract the parameter (instance) type in the constructor:

    A constructor can be instantiated using new, so its type is usually expressed as follows:

    type Constructor = new(... args:any[]) = >any;
    Copy the code

    Infer, when used in constructor types, can be used at the parameter position new (… args: infer P) => any; And return value position new (… args: any[]) => infer P; .

    Therefore, the following two mapping types are built in:

    // Get the parameter type
    type ConstructorParameters<T extends new(... args:any[]) = >any> = T extends new(... args: infer P) =>any ? P : never;
    
    // Get the instance type
    type InstanceType<T extends new(... args:any[]) = >any> = T extends new(... args:any[]) => infer R ? R : any;
    
    class TestClass {
    
      constructor(
        public name: string.public string: number
      ) {}}type Params = ConstructorParameters<typeof TestClass>;  // [string, numbder]
    
    type Instance = InstanceType<typeof TestClass>;         // TestClass
    Copy the code

Some cases

Now that you have a basic understanding of Infer, let’s look at some of the ‘slushie’ operations that use infer:

  • Turn the tuple union, such as: [string, number] – > string | number

    To answer this question, we need to know that tuple can be assigned to an array type under certain conditions:

    type TTuple = [string.number];
    type TArray = Array<string | number>;
    
    type Res = TTuple extends TArray ? true : false;    // true
    type ResO = TArray extends TTuple ? true : false;   // false
    Copy the code

    So, in conjunction with infer, it is easy to do:

    type ElementOf<T> = T extends Array<infer E> ? E : never
    
    type TTuple = [string.number];
    
    type ToUnion = ElementOf<TTuple>; // string | number
    Copy the code

    See another solution on StackOverflow, which is simpler:

    type TTuple = [string.number];
    type Res = TTuple[number];  // string | number
    Copy the code
  • Turn the union intersection computes, such as: string | number – > string & number

    This can be a little more difficult and requires infer to be used with “Distributive Conditional types”.

    In the related links, we can learn that “Distributive Conditional types” are conditional types composed of “Naked type Parameters”. The “Naked type parameter” indicates the type that is not Wrapped (e.g., Array

    , [T], Promise

    , etc., are not “naked type parameters”). “Distributive Conditional types” are mainly used to split union types on the left side of extends, for example: in the condition type T extends U? X: Y, when T is A | B will be split into A extends U? X : Y | B extends U ? X: Y;

    Given this premise, multiple candidate types of a variable of the same type will be inferred as intersecting type properties in contravariant positions, i.e

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

    So, combining these points, we can get an answer on StackOverflow:

    type UnionToIntersection<U> =
      (U extends any ? (k: U) = >void : never) extends ((k: infer I) = >void)?I : never;
    
    type Result = UnionToIntersection<string | number>; // string & number
    Copy the code

    When the incoming string | number:

    • Step 1 :(U extends any? (k: U) => void: never) splits union into (string extends any? (k: string) => void : never) | (number extends any ? (k: number) = > void: never), is to get (k: string) = > void | (k: number) = > void;

    • The second step: k: string) = > void | (k: number) = > void extends ((k: infer I)) = > void? I: never. We can infer that I is string & number.

Of course, you can do a lot more things, like union tuple.

A TypeScript interview question from LeetCode

Some time ago, on GitHub, I found an interesting interview question from LeetCode TypeScript, which basically means:

Suppose you have a type that looks like this (class, simplified to interface) :

interface Module {
  count: number;
  message: string;
  asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
  syncMethod<T, U>(action: Action<T>): Action<U>;
}
Copy the code

After the Connect function, the return value is of type

type Result {
  asyncMethod<T, U>(input: T): Action<U>;
  syncMethod<T, U>(action: T): Action<U>;
}
Copy the code

Where Action

is defined as:

interfaceAction<T> { payload? : Ttype: string
}
Copy the code

There are two main observations here

  • Pick out the function
  • Condition type + as mentioned in this articleinfer

The method of selecting functions has been given in Handbook, just judge that value can be assigned to Function:

type FuncName<T>  = {
  [P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];

type Connect = (module: Module) = > { [T in FuncName<Module>]: Module[T] }
/* * type Connect = (module: Module) => { * asyncMethod: 
      
       (input: Promise
       
        ) => Promise
        
         >; * syncMethod: 
         
          (action: Action
          
           ) => Action
           ; *} * /
          
         ,>
        
       
      ,>
Copy the code

Infer: asyncMethod

(input: Promise

) Promise

>, then asyncMethod

(input: T): Action
. The specific answer is not given, interested partners can try.
,>


,>

More and more

  • Understand TypeScript in depth
  • Working with TypeScript (4)
  • Working with TypeScript (3)
  • Working with TypeScript (2)
  • Working with TypeScript

reference

  • Get the tuple from unit
  • TypeScript 2.8
  • unit to intersection

For more articles, please pay attention to our official account: