Дженерики (Generics)
Дженерики (Generics)
Технический фундамент параметров типа
Generics дают способ связать несколько мест контракта одним типовым параметром:
- вход и выход функции могут быть строго связаны через
T; - ограничения (
extends) защищают от слишком широких сценариев; - вывод generic-типа часто происходит автоматически из аргументов;
- цель дженерика не "усложнить тип", а сохранить точность в переиспользуемом API.
Если generic не связывает реальные части контракта, он обычно избыточен.
Зачем нужны дженерики
Без дженериков ты часто выбираешь между двумя плохими вариантами: дублировать функции под каждый тип или писать слишком общий код на any. Generics позволяют сделать один переиспользуемый шаблон, который сохраняет точную типизацию для каждого конкретного вызова.
Ключевой момент: дженерик это "параметр типа", как переменная, но для типов.
Проверь себя: почему any в утилитарной функции может сломать типовую безопасность всего потока данных?
Базовый пример generic-функции
function identity<T>(value: T): T {
return value;
}
const a = identity<number>(10);
const b = identity('hello'); // тип выведется автоматически
Смотри, что важно: функция возвращает точно тот же тип, который получила.
Generics и массивы
function firstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstUser = firstItem([{ id: 1 }, { id: 2 }]);
const firstName = firstItem(['Ann', 'Max']);
Одна функция работает для разных типов массива без потери точности.
Ограничения generic (extends)
Иногда generic слишком общий, и нужны ограничения.
function getId<T extends { id: string | number }>(entity: T) {
return entity.id;
}
Новый термин: constraint - ограничение на допустимые типы параметра T.
Проверь себя: зачем ограничивать T, если можно оставить его любым?
keyof и безопасный доступ к свойствам
Частый прод-паттерн: написать функцию, которая берет объект и имя поля, и при этом возвращает правильный тип значения этого поля.
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Anna' };
const id = getProp(user, 'id'); // number
const name = getProp(user, 'name'); // string
// getProp(user, 'age'); // Ошибка: "age" нет в keyof typeof user
Смотри, что важно: keyof T это "множество" (union) допустимых ключей, а T[K] это тип значения по ключу.
Generic-интерфейсы и типы
interface ApiResponse<T> {
data: T;
error: string | null;
}
const userResponse: ApiResponse<{ id: number; name: string }> = {
data: { id: 1, name: 'Anna' },
error: null,
};
Это частый прод-сценарий: единая форма ответа с разным типом data.
Generics в классах
class StorageBox<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numBox = new StorageBox<number>(42);
Класс становится переиспользуемым и остается типобезопасным.
Реальные микро-сценарии
- Универсальная функция
fetchJson<T>(), возвращающая типизированные данные API. - Generic-таблица UI, где строки имеют разные структуры в разных экранах.
- Кеш-хранилище, где тип значения задается при создании.
Частые ошибки новичков
- Использовать generic, когда достаточно конкретного типа.
- Забывать ограничения
extendsи получать слишком широкий контракт. - Перегружать код сложными generic-цепочками без пользы.
- Подменять generics
any, теряя все преимущества.
Анти-провал: применяй дженерики только там, где реально нужен переиспользуемый шаблон с сохранением типа.
Что будет, если изменить входные данные
Если identity получает объект { id: 1 }, TypeScript сохранит его точную форму на выходе. Если функция была бы на any, ты легко потерял бы свойства или передал бы несовместимый тип дальше без предупреждения.
Проверь себя: в каком случае generic помогает рефакторить без страха сломать типы в цепочке вызовов?
Краткий итог
- Generics позволяют писать универсальный код без потери типовой точности.
- Параметр типа
Tсвязывает входы и выходы функции/класса. - Ограничения
extendsделают generic-контракты безопаснее. - Generic-интерфейсы особенно полезны для API, утилит и инфраструктурного кода.
- Главная цель дженериков - переиспользование без отказа от строгой типизации.