Generic функции
Generic функции — функции с типовыми параметрами
<T>, которые работают с разными типами данных, сохраняя связь между входными и выходными типами, которую компилятор выводит автоматически при вызове.
Зачем нужно
Без generics универсальные функции пишут с any, теряя типобезопасность. Generic функции позволяют написать код один раз и использовать с разными типами, при этом TypeScript отслеживает конкретный тип в каждом вызове. Это основа переиспользуемых утилит.
Где используется
- Утилиты коллекций:
first,last,groupBy,unique - Обёртки над fetch/API с типизированным ответом
- HOF (функции высшего порядка):
map,filter,reduce - Функции трансформации и маппинга данных
- Promise-утилиты:
retry,timeout,memoize
Основной контент
Базовый синтаксис
// Обычная функция
function identity<T>(arg: T): T {
return arg;
}
// Стрелочная функция (в .tsx нужно <T,> или <T extends unknown>)
const identity = <T>(arg: T): T => arg;
// Явный вызов с типом
const s = identity<string>("hello"); // string
// Вывод типа (type inference) — TypeScript определяет T автоматически
const n = identity(42); // number
Несколько типовых параметров
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const p = pair("name", 42); // [string, number]
const q = pair(true, { id: 1 }); // [boolean, { id: number }]
Generic функции с ограничениями
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, "missing"); // Error — "missing" не в keyof User
Практические утилиты
// first — первый элемент массива
function first<T>(arr: T): T | undefined {
return arr[0];
}
// unique — дедупликация
function unique<T>(arr: T): T {
return [...new Set(arr)];
}
// groupBy — группировка по ключу
function groupBy<T, K extends string | number>(
arr: T,
keyFn: (item: T) => K
): Record<K, T> {
return arr.reduce((acc, item) => {
const key = keyFn(item);
(acc[key] ??= ).push(item);
return acc;
}, {} as Record<K, T>);
}
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Carol", role: "admin" },
];
const byRole = groupBy(users, (u) => u.role);
// { admin: [...], user: [...] }
Generic fetch-обёртка
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
}
interface User { id: number; name: string; }
const user = await fetchJson<User>("/api/users/1");
// user: User — типобезопасно
console.log(user.name);
Вывод типов в generic функциях
// TypeScript выводит T из аргументов
function wrap<T>(value: T): { value: T } {
return { value };
}
const a = wrap("hello"); // { value: string }
const b = wrap([1, 2, 3]); // { value: number }
// Явное указание нужно когда inference не работает
const c = wrap<string | number>("hello"); // { value: string | number }
Частые ошибки
- Писать
<T extends any>— это то же что не писать ограничение вообще; лучше<T>или конкретный constraint. - Не использовать K extends keyof T для доступа к полям объекта — без этого TypeScript не сможет вывести тип возвращаемого значения.
- Дублировать код вместо generic — если функции
getNumberиgetStringотличаются только типом, это кандидат на generic. - Усложнять generics без необходимости — если функция принимает и возвращает одно и то же,
<T>уместен; если возврат всегдаstring— generic избыточен.
Связанные темы
- Generic интерфейсы и классы
- Ограничения дженериков -- extends
- Дженерики -- практические примеры
- Utility types
- _MOC TypeScript