The `infer` keyword and type extraction
Basic Concept of the infer
Keyword
The infer
keyword in TypeScript is used to declare a type variable to be inferred within conditional types. It allows us to perform pattern matching in the type system, extracting the parts we care about from complex types. infer
is typically used with conditional types, especially when working with function types, array types, and Promise types.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
This simple example demonstrates the basic usage of infer
: we declare a type variable R
, which represents the return type of a function type. If T
is a function type, it returns R
; otherwise, it returns never
.
How infer
Works
The infer
keyword operates similarly to pattern matching in the type system. When the TypeScript compiler encounters infer
, it attempts to "deconstruct" the matching part from the given type. This process can be understood as:
- Checking whether the type matches the pattern in the conditional type.
- If it matches, binding the type variable declared by
infer
to the corresponding part. - Using this inferred type in the true branch of the conditional type.
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type Num = UnpackPromise<Promise<number>>; // number
type Str = UnpackPromise<string>; // string
In this example, the UnpackPromise
type checks whether T
is a Promise
type. If it is, it extracts the wrapped type U
; if not, it returns T
itself.
Common Use Cases
Extracting Function Return Types
ReturnType
is a built-in utility type in TypeScript that uses infer
to extract a function's return type:
function getUser() {
return { name: 'Alice', age: 30 };
}
type User = ReturnType<typeof getUser>;
// { name: string; age: number; }
Extracting Function Parameter Types
Similarly, we can extract the types of function parameters:
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number) {
return `Hello, ${name}! You are ${age} years old.`;
}
type GreetParams = Parameters<typeof greet>; // [string, number]
Extracting Array Element Types
infer
is also very useful when working with array types:
type ElementType<T> = T extends (infer U)[] ? U : T;
type Num = ElementType<number[]>; // number
type Str = ElementType<string[]>; // string
type Bool = ElementType<boolean>; // boolean
Extracting Promise Resolution Types
When working with asynchronous code, it's often necessary to extract the type resolved by a Promise:
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Num = Awaited<Promise<number>>; // number
type Str = Awaited<string>; // string
Advanced Usage
Recursive Type Extraction
infer
can be used to build recursive types for handling nested structures:
type DeepUnpackPromise<T> =
T extends Promise<infer U>
? DeepUnpackPromise<U>
: T;
type Num = DeepUnpackPromise<Promise<Promise<number>>>; // number
infer
in Union Types
Using infer
in union types can extract common parts:
type CommonReturn<T> =
T extends (...args: any[]) => infer R ? R : never;
type F1 = () => string;
type F2 = () => number;
type R = CommonReturn<F1 | F2>; // string | number
Tuple Type Manipulation
infer
can be used to manipulate tuple types:
type First<T extends any[]> = T extends [infer U, ...any[]] ? U : never;
type Rest<T extends any[]> = T extends [any, ...infer U] ? U : never;
type F = First<[string, number, boolean]>; // string
type R = Rest<[string, number, boolean]>; // [number, boolean]
Practical Application Examples
Implementing a Simple State Machine Type
type StateTransitions = {
idle: { type: 'start'; payload: string };
running: { type: 'pause' } | { type: 'stop'; payload: number };
paused: { type: 'resume' } | { type: 'stop'; payload: number };
};
type EventType<T> = T extends { type: infer U } ? U : never;
type EventPayload<T, K> = T extends { type: K; payload: infer P } ? P : never;
type IdleEvent = EventType<StateTransitions['idle']>; // 'start'
type StartPayload = EventPayload<StateTransitions['idle'], 'start'>; // string
Building Reactive Form Types
type FormField<T> = {
value: T;
isValid: boolean;
error?: string;
};
type ExtractFormValue<T> = T extends FormField<infer U> ? U : never;
type UsernameField = FormField<string>;
type PasswordField = FormField<string>;
type Username = ExtractFormValue<UsernameField>; // string
type Password = ExtractFormValue<PasswordField>; // string
Combining with Other Advanced Types
Combining with Mapped Types
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
class User {
name: string;
age: number;
greet() { return `Hello`; }
}
type UserMethods = FunctionProperties<User>; // { greet: () => string; }
Combining with Conditional Type Distribution
type Box<T> = { value: T };
type Unbox<T> = T extends Box<infer U> ? U : T;
type A = Unbox<Box<number>>; // number
type B = Unbox<string>; // string
type C = Unbox<Box<string> | Box<number>>; // string | number
Notes and Limitations
Position Restrictions for infer
infer
declarations can only appear in covariant positions (such as function return types or Promise resolution types) within the extends
clause of a conditional type, not in contravariant positions (such as function parameter types):
// Incorrect usage - `infer` in a contravariant position
type ParamType<T> = T extends (infer P) => any ? P : never;
// Correct usage
type ParamType<T> = T extends (...args: infer P) => any ? P : never;
Using Multiple infer
Declarations
You can use multiple infer
declarations in a single conditional type:
type FlipArgs<T> =
T extends (...args: infer A) => infer R
? (...args: Reverse<A>) => R
: never;
// Assuming `Reverse` is a type that can reverse a tuple
type Reversed = FlipArgs<(a: string, b: number) => boolean>;
// Type becomes (b: number, a: string) => boolean
Depth Limitations for Type Inference
TypeScript has some limitations on deeply nested type inference. Overly complex types may cause performance issues or inference failures:
// This type may encounter issues with deep nesting
type DeepInfer<T> =
T extends Promise<infer U>
? DeepInfer<U>
: T extends Array<infer V>
? DeepInfer<V>[]
: T;
Practical Tips for Type Extraction
Extracting Constructor Types
type ConstructorParameters<T> =
T extends new (...args: infer P) => any ? P : never;
class Person {
constructor(public name: string, public age: number) {}
}
type PersonParams = ConstructorParameters<typeof Person>; // [string, number]
Extracting Instance Types
type InstanceType<T> =
T extends new (...args: any[]) => infer R ? R : any;
type PersonInstance = InstanceType<typeof Person>; // Person
Handling Overloaded Functions
For overloaded functions, infer
will match the last overload signature:
function overloaded(a: string): number;
function overloaded(a: number): string;
function overloaded(a: any): any {
return typeof a === 'string' ? a.length : a.toString();
}
type OverloadedReturn = ReturnType<typeof overloaded>; // string
Applications of Type Extraction in Library Development
Implementing a Simple DI Container Type
interface ServiceContainer {
get<T>(key: string): T;
}
type ServiceKeys<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => infer R ? R : never;
};
function createContainer<T>(services: T): ServiceContainer & ServiceKeys<T> {
// Implementation omitted
return {} as any;
}
const container = createContainer({
userService: () => ({ getUser: (id: number) => ({ id, name: 'Alice' }) }),
logger: () => ({ log: (msg: string) => console.log(msg) })
});
type UserService = typeof container['userService']; // { getUser: (id: number) => { id: number; name: string; } }
type Logger = typeof container['logger']; // { log: (msg: string) => void }
Building a Type-Safe Redux Reducer
type Action<T extends string = string, P = any> = {
type: T;
payload?: P;
};
type ActionCreator<T extends string, P> = (payload?: P) => Action<T, P>;
type ExtractActionType<AC> = AC extends ActionCreator<infer T, any> ? T : never;
type ExtractActionPayload<AC, T> = AC extends ActionCreator<T, infer P> ? P : never;
function createAction<T extends string, P>(type: T): ActionCreator<T, P> {
return (payload?: P) => ({ type, payload });
}
const increment = createAction('INCREMENT');
const setValue = createAction('SET_VALUE', (value: string) => value);
type IncrementType = ExtractActionType<typeof increment>; // 'INCREMENT'
type SetValuePayload = ExtractActionPayload<typeof setValue, 'SET_VALUE'>; // string
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn