Обобщённые типы (Generics)
Обобщённые типы (generics) в TypeScript позволяют писать переиспользуемый и типобезопасный код, не привязываясь к конкретному типу данных. Они особенно полезны при работе с функциями, коллекциями и компонентами, которые должны работать с разными типами, сохраняя при этом точность типизации.
Проблема без generics
Рассмотрим функцию, которая возвращает переданное ей значение:
function identity(arg: any): any {
return arg;
}
Такая функция компилируется, но теряет информацию о типе. Если передать строку, TypeScript не сможет гарантировать, что на выходе тоже строка — только any.
Это нарушает принцип типовой безопасности.
Введение generics
Generics решают эту проблему, позволяя «захватывать» тип аргумента и использовать его в аннотации возвращаемого значения.
function identity<T>(arg: T): T {
return arg;
}
Здесь <T> — это параметр типа. Он работает как переменная для типа. При вызове функции TypeScript выводит T автоматически:
const output = identity("привет"); // T выведено как string
// output имеет тип string
Можно также указать тип явно:
const output = identity<string>("привет");
Generics в функциях с массивами
Часто generics используются при работе с коллекциями. Например, функция, возвращающая длину массива:
function getLength<T>(items: T[]): number {
return items.length;
}
const numbers = [1, 2, 3];
const count = getLength(numbers); // T = number, items: number[]
Функция работает с любым массивом, но сохраняет знание о том, какие элементы в нём лежат (даже если сама их не использует).
Ограничения generics (extends)
Иногда нужно гарантировать, что generic-тип имеет определённые свойства. Для этого используется ограничение через extends.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("текст"); // OK: у строки есть length
logLength([1, 2, 3]); // OK: у массива есть length
// logLength(42); // Ошибка: число не имеет length
Это позволяет безопасно использовать свойства внутри функции.
Generics в типах и интерфейсах
Generics можно использовать не только в функциях, но и в определении типов:
type ApiResponse<T> = {
data: T;
status: number;
};
interface Repository<T> {
save(item: T): void;
findById(id: string): T | null;
}
Пример использования:
const userResponse: ApiResponse<User> = {
data: { name: "Иван", email: "ivan@example.com" },
status: 200
};
Несколько параметров типа
Функция или тип может принимать несколько generic-параметров:
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const result = pair("имя", 25); // тип: [string, number]
Порядок и имена параметров (A, B, T, U и т.д.) выбираются по смыслу или соглашению.