TypeScript infer Keyword
infer is a keyword in TypeScript conditional types, used to infer new types from existing types.
It allows extracting and deriving types within generic conditional types, enabling powerful type operations.
SVG Diagram: How infer Works
Why is the infer keyword needed?
In generic programming, we often need to extract partial types from complex types.
For example, fromPromise<string>extractstring, fromArray<User>extractUser。
The infer keyword makes such type extraction elegant and type-safe. It allows declaring a type variable in a conditional type, then using it in the true branch.
Concept explanation:infer is the abbreviation of "infer", meaning "to infer". It can only be used in the extends clause of a conditional type to declare a type variable to be inferred.
Basic Usage
Use infer to infer types in conditional types.
Example
// If T is Promise<V>, return V; otherwise return never
type ValueOf<T> = T extends Promise<infer V> ? V : never;
// Test
type Str = ValueOf<Promise<string>>; // string
type Num = ValueOf<Promise<number>>; // number
type NotPrm = ValueOf<string>; // never
// Usage example
var promise: Promise<string> = Promise.resolve("hello");
var value: ValueOf<typeof promise> = "world";
console.log("String extraction: " + (typeof value === "string" ? "Success" : "Failure"));
Output:
String extraction: success
Explanation:
infer VA type variable V is declared, and TypeScript will automatically infer the type of V.
Extracting Array Element Types
Extract the element type from an array type.
Example
// If T is an array V[], return V
type ArrayElement<T> = T extends (infer V)[] ? V : never;
// Test
type User = { name: string };
type Users = User[];
type E1 = ArrayElement<string[]>; // string
type E2 = ArrayElement<Users>; // User
type E3 = ArrayElement<number>; // never (non-array)
// Usage example
var users: Users = [{ name: "Alice" }];
var element: ArrayElement<Users> = users[0];
console.log("Element type: " + element.name);
Use cases:This pattern is often used to extract elements of a specific type from union types.
Extracting Function Return Types
Extract the return type from a function type.
Example
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// Test
function getData() {
return { id: 1, name: "Alice" };
}
function fetchUser(id: number): Promise<User> {
return Promise.resolve({ id, name: "Bob" });
}
type R1 = ReturnType<typeof getData>; // { id: number; name: string }
type R2 = ReturnType<typeof fetchUser>; // Promise<User>
// Usage example
var result: R1 = { id: 1, name: "Test" };
console.log("Return type: " + JSON.stringify(result));
Built-in types:TypeScript's built-in
ReturnType<T>utility types are implemented using infer.
Extracting Function Parameter Types
Extract parameter types from a function type.
type FirstParameter<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
// Test
function createUser(name: string, age: number): User {
return { id: 1, name };
}
function logMessage(msg: string): void {
console.log(msg);
}
type P1 = FirstParameter<typeof createUser>; // string
type P2 = FirstParameter<typeof logMessage>; // string
type P3 = FirstParameter<() => void>; // never
// Usage example
var nameParam: P1 = "Alice";
console.log("Parameter type: " + nameParam);
Note:You can use
...rest: any[]to match any number of remaining parameters.
Multiple infer
Using multiple infer in a conditional type.
Example
type FirstTwo<T> = T extends [infer A, infer B, ...rest: any[]] ? [A, B] : never;
// Test
type Tuple = [string, number, boolean];
type FirstTwoTypes = FirstTwo<Tuple>; // [string, number]
// Extract object properties
type ObjectValue<T> = T extends { value: infer V } ? V : never;
type WithValue = { value: string; name: string };
type ExtractedValue = ObjectValue<WithValue>; // string
// Usage example
var tupleResult: FirstTwoTypes = ["hello", 123];
console.log("Tuple: " + JSON.stringify(tupleResult));
Tuple:Use
...rest: any[]to match the remaining elements of a tuple.
Using infer in Recursive Types
Use infer in recursive type utilities to implement complex type operations.
Example
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
// Test
interface User {
name: string;
address: {
city: string;
zip: string;
};
}
type ReadonlyUser = DeepReadonly<User>;
// Flatten Promise types
type FlattenPromise<T> = T extends Promise<infer U>
? U extends Promise<any>
? FlattenPromise<U>
: U
: T;
type Nested = Promise<Promise<string>>;
type Flat = FlattenPromise<Nested>; // string
// Usage example
var user: ReadonlyUser = {
name: "Alice",
address: { city: "Beijing", zip: "100000" }
};
// user.name = "Bob"; // Error: readonly
console.log("Deep readonly: " + user.name);
Recursive infer:Using infer in recursive types enables deep type transformations, such as deep readonly, deep partial, etc.
Notes
- Can only be used on the right side of extends:infer can only be used in the extends clause of a conditional type
- Declaring type variables:Use
infer Vto declare the type variable to be inferred. - Multiple uses allowed:Multiple infer can be used in a single conditional type
- Returns never if inference fails:If inference fails, the conditional type returns never
Best practices:infer is the core of implementing custom utility types; mastering it enables powerful type operations.
Summary
infer is one of the most powerful features in the TypeScript type system.
- Type extraction:Extracting partial types from complex types
- Conditional inference:Inferring types within conditional types
- Function-related:Extracting function parameters and return types
- Recursive utilities:Implementing deep type transformations
Other ExtensionsRecommendation:A deep understanding of infer allows you to write more advanced type utilities, improving type safety and development efficiency.