infer оголошує змінну типу всередині умови умовного типу: «якщо T має таку форму, дістань звідти ось цю частину».
type ElementOf<T> = T extends readonly (infer U)[] ? U : never;
type A = ElementOf<string[]>; // string
type B = ElementOf<readonly [1, 'a']>; // 1 | 'a'
type C = ElementOf<number>; // never - не масив
Так влаштовані вбудовані утиліти:
type ReturnType<T> = T extends (...args: any) => infer R ? R : any;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type Awaited<T> = /* рекурсивно розгортає Promise і thenable */;
Практичні приклади:
// тип даних з функції, що повертає проміс
type Data<T> = T extends (...args: any[]) => Promise<infer D> ? D : never;
type UserData = Data<typeof fetchUser>;
// перший аргумент функції
type FirstArg<F> = F extends (first: infer A, ...rest: any[]) => any ? A : never;
// тип props React-компонента
type PropsOf<C> = C extends (props: infer P) => any ? P : never;
infer з шаблонними рядками - розбір рядкових типів:
type RouteParams<Path extends string> =
Path extends `${string}:${infer Param}/${infer Rest}`
? Param | RouteParams<`/${Rest}`>
: Path extends `${string}:${infer Param}`
? Param
: never;
type P = RouteParams<'/teams/:teamId/users/:userId'>; // 'teamId' | 'userId'
Так роутери (React Router, TanStack Router) типізують параметри маршрутів з рядка шляху.
infer з обмеженням (TS 4.7+) - дістати лише якщо підходить під тип:
type FirstString<T> = T extends [infer S extends string, ...unknown[]] ? S : never;
Що варто пам'ятати:
inferпрацює лише в частиніextendsумовного типу;- якщо структура не збіглася, спрацьовує гілка «інакше» - зазвичай
never, і про це легко забути: тип мовчки стаєnever, а не помилкою; - кілька кандидатів для однієї змінної (
infer Uу кількох позиціях) дають об'єднання чи перетин залежно від позиції (коваріантна чи контраваріантна).
Перевага: не потрібно експортувати допоміжні типи з бібліотек - їх можна витягти з типів функцій і компонентів.