infer
| Infer inside conditions | |
| Infer return types | |
| Infer arguments | |
| Unwrap promises |
Infer inside conditions
infer declares a fresh type variable inside a condition and captures whatever sits in its spot. Think of it as pattern matching for types.
First matches an array of something, names that something E, and returns it. A non-array falls through to never.
type First<T> = T extends Array<infer E> ? E : never;
type F = First<Array<string>>; // string
Infer return types
Return positions work the same way. Match the function shape, capture the result with infer R, and return it.
This mirrors the built-in ReturnType, where the any array accepts every argument list. N resolves to string for a function that returns a string.
type Returns<F> = F extends (...args: any[]) => infer R ? R : never;
type N = Returns<(id: number) => string>; // string
Infer arguments
Parameter positions can be captured too. Match the first parameter with infer A and let the rest collect the remainder.
S resolves to string because the example takes a name first. This is the same idea behind the built-in Parameters helper.
type FirstArg<F> = F extends (first: infer A, ...rest: unknown[]) => unknown ? A : never;
type S = FirstArg<(name: string) => number>; // string
Unwrap promises
Promises nest, and infer can peel every layer with recursion. If the type is a Promise, capture the inside and unwrap again, else stop.
U resolves to plain string no matter how deep the nesting goes. The built-in Awaited type follows the same recipe.
type Unwrap<T> = T extends Promise<infer V> ? Unwrap<V> : T;
type U = Unwrap<Promise<Promise<string>>>; // string
Next: Template literal types
Article author: Arthur Isaev