Before we start with the infer keyword, let’s look at an example of conditional type inference without infer.

Example of condition type constraints

Write a type Flatten to Flatten array types to their element types, otherwise keep them:



type Flatten<T> = T extends Array<any>? T[number] : T;



type Str = Flatten<Array<string> >;

// type Str = string



type Num = Flatten<number>;

// type Num = number



Copy the code

When Flatten is given Array

, it uses the index number to get the element type of the Array. Otherwise, return the given type.

Built-in types

When you write TS code, you don’t use the built-in type ReturnType. Are you curious about the internal implementation when you use it?

Conditional types provide us with a way to infer from types we compare against in the true branch using the infer keyword.

Conditional types give us a way to infer from types compared in true branches using the infer keyword.



// The return value type used to extract the function type

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



Copy the code

This is the principle of ReturnType implementation, is there a sense of enlightenment, is that simple.



type Func = (a)= > string;

type Test = ReturnType<Func>;

// Test = string



Copy the code

ReturnType on the official website

Infer using infer

Conditional types give us a way to infer type comparisons from real branches using the Infer keyword. For example, we can infer that element types are no longer obtained “manually” by Flatten using index access types:



type Flatten<T> = T extends Array<infer U> ? U : T;



// Flatten<string> <=> string

// Flatten<Array<number>> <=> number



Copy the code

We use the Infer keyword to declaratively introduce a new generic type variable named Infer U to represent function parameters to be inferred

1. If T can be assigned to Array

, the result is the type U from Array

, otherwise T is returned.

Using the example

Infer: Now you have a basic understanding of infer, let’s look at a little usage technique

“Tuple” turn “the union”, such as: [string, number] – > string | number

Before we answer, let’s look at how tuple is 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

Remember Flatten up here?



type Flatten<T> = T extends Array<any>? T[number] : T;

/ / note T [number]



Copy the code

Of course, the “tuple” to “union” can also be used in this way:



type TTuple = [string.number];

type Res = TTuple[number];  // string | number



Copy the code

Do you feel this wave operation is very cool.

An interview question

The topic is as follows:



interface Action<T> {

payload? : T;

  typestring;

}



// Suppose you have an interface like Modle

interface Module {

  count: number;

  message: string;

  asyncMethod<T, U>(action: Promise<T>): Promise<Action<U>>;

  syncMethod<T, U>(action: Action<T>): Action<U>;

}





// Implement type Connect

// Leave attributes as function types, discard others

(args: T) => Action ,>

type Connect<T> = /** The logic you need to implement */



type Result = Connect<Module>;



// Result = {

// asyncMethod (input: T): Action ; ,>

// syncMethod (action: T): Action ; ,>

// }



Copy the code

There are two main observations here

  • Pick out the function
  • Condition type + Infer is mentioned in this paper

1. Implement a property whose type can be filtered out as a function type.

type PickFuncProp<T> = {

  [P in keyof T]: T[P] extends Function ? P : never;

}[keyof T];



// PickFuncProp<Module> <=> 'asyncMethod'|'syncMethod'

Copy the code

2. Infer is used to convert functions.



type TransitionFunc<F> = F extends (action: Promise<infer T>) => Promise<Action<infer U>>

  ? <T, U>(action: T) = > Action<U>

  : F extends (action: Action<infer T>) => Action<infer U>

  ? <T,U>(action: T) = > Action<U>

  : F;





type syncMethod<T, U> = (action: Action<T>) = > Action<U>;

type asyncMethod<T, U> = (input: Promise<T>) = > Promise<Action<U>>;



// TransitionFunc<syncMethod> <=> <T, U>(action: T) => Action<U>;

// TransitionFunc<asyncMethod> <=> <T, U>(action: T) => Action<U>;



Copy the code

3. The next step is to combine PickFuncProp and TransitionFunc to implement the Connect method.



type Connect<T> = {

  [P in PickFuncProp<T>]: TransitionFunc<T[P]>;

};



type Result = Connect(Module);



// Result = {

// asyncMethod: (action: T) => Action ; ,>

// syncMethod: (action: T) => Action ; ,>

// }

Copy the code

References:

  • Leetcode typescript interview questions
  • Typescript official address