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;
type: string;
}
// 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