Generics в TypeScript часто объясняют так, что новичок успевает испугаться раньше, чем увидит пользу. Угловые скобки, буква T, какие-то ограничения, keyof, extends. Кажется, будто это тема для людей, которые пишут библиотеки, а не обычный код
На самом деле первая идея простая: generic нужен, когда функция или тип должны работать с разными данными, но не терять информацию о конкретном типе
Если сказать еще проще: мы хотим переиспользовать код и не скатываться в any
- Проблема без generics
- Первый generic
- Почему T, а не другое имя
- Generic type для ответа API
- Generic с ограничением extends
- keyof: взять ключ объекта
- Когда generic не нужен
- Частые ошибки
- Мини-задание
- Что может быть еще интересно по этой теме
- Generics нужны только для библиотек?
- Почему не использовать any?
- Можно ли назвать T по-другому?
- Когда стоит остановиться?
- Что почитать дальше по TypeScript
Проблема без generics
Допустим, нужна функция, которая возвращает первый элемент массива:
function firstString(items: string[]): string {
return items[0];
}
function firstNumber(items: number[]): number {
return items[0];
}
Код почти одинаковый. Отличается только тип
Можно написать через any:
function firstAny(items: any[]): any {
return items[0];
}
Но мы потеряли пользу TypeScript. Если передать массив строк, TypeScript уже не помнит, что результат строка
const title = firstAny(["TypeScript", "JavaScript"]);
title.toFixed(2);
TypeScript не остановит, потому что title стал any. А у строки нет toFixed
Первый generic
Напишем универсальную функцию:
function first<T>(items: T[]): T {
return items[0];
}
T — это параметр типа. Можно читать так: "функция работает с массивом каких-то элементов T и возвращает один элемент T"
Примеры:
const title = first(["TypeScript", "JavaScript"]);
const score = first([10, 20, 30]);
TypeScript понимает:
title— string;score— number.
Мы не писали две функции и не использовали any
Почему T, а не другое имя
T — просто традиционное имя. Можно написать:
function first<Item>(items: Item[]): Item {
return items[0];
}
Так иногда даже понятнее. В маленьких примерах часто используют T, в более сложных типах лучше давать говорящие имена: Item, ResponseData, Entity
Главное — не думать, что T волшебная буква. Это переменная для типа
Generic type для ответа API
Очень практический пример — ответ API. Допустим, сервер всегда возвращает такую обертку:
{
"data": "...",
"success": true,
"error": null
}
Но data бывает разной: пользователь, список уроков, заказ
Можно описать generic:
type ApiResponse<Data> = {
data: Data;
success: boolean;
error: string | null;
};
Теперь пользователь:
type User = {
id: number;
name: string;
};
const userResponse: ApiResponse<User> = {
data: {
id: 1,
name: "Алина",
},
success: true,
error: null,
};
А список уроков:
type Lesson = {
title: string;
minutes: number;
};
const lessonsResponse: ApiResponse<Lesson[]> = {
data: [
{ title: "Generics", minutes: 30 },
],
success: true,
error: null,
};
Одна обертка, разные данные, типы не потерялись
Generic с ограничением extends
Иногда generic слишком свободный. Например, функция должна работать с объектами, у которых точно есть id
Плохой вариант:
function printId<T>(item: T): string {
return `ID: ${item.id}`;
}
TypeScript ругнется: он не знает, что у любого T есть id
Добавим ограничение:
function printId<T extends { id: number | string }>(item: T): string {
return `ID: ${item.id}`;
}
Теперь функция принимает не что угодно, а только значения с id
printId({ id: 10, title: "TypeScript" });
printId({ name: "No id" });
Второй вызов подсветится. Это и есть смысл extends: тип должен подходить под минимальную форму
keyof: взять ключ объекта
Еще один полезный пример:
function getValue<T, Key extends keyof T>(object: T, key: Key): T[Key] {
return object[key];
}
На первый взгляд плотновато, разберем:
T— тип объекта;Key extends keyof T— ключ должен быть одним из ключей объекта;T[Key]— тип значения по этому ключу.
Пример:
const lesson = {
title: "Generics",
minutes: 30,
};
const title = getValue(lesson, "title");
const minutes = getValue(lesson, "minutes");
const missing = getValue(lesson, "author");
"author" подсветится, потому что такого ключа нет
Это уже не первый шаг, но хороший пример пользы generics: TypeScript связывает ключ и результат
Когда generic не нужен
Не надо писать generic ради красоты:
function double<T>(value: T): T {
return value * 2;
}
Такой код неправильный по смыслу. Умножать можно не любой T
Лучше честно:
function double(value: number): number {
return value * 2;
}
Generic нужен, когда тип действительно должен оставаться гибким: массив любых элементов, API-обертка, переиспользуемый компонент, функция выбора значения из объекта
Частые ошибки
Первая ошибка — заменять generics на any. Код вроде проще, но TypeScript перестает помнить тип результата
Вторая ошибка — добавлять generic там, где достаточно обычного number или string
Третья ошибка — использовать одну букву в сложном типе, где лучше дать имя. ResponseData иногда читается лучше, чем T
Четвертая ошибка — забывать ограничения extends. Если функция работает только с объектами с id, это нужно сказать TypeScript
Мини-задание
Напиши generic-тип:
type ListResult<Item> = {
items: Item[];
total: number;
};
Потом опиши урок:
type Lesson = {
id: number;
title: string;
};
И создай результат:
const result: ListResult<Lesson> = {
items: [
{ id: 1, title: "Generics" },
],
total: 1,
};
После этого специально убери title из объекта и посмотри, как TypeScript подсветит ошибку
Что может быть еще интересно по этой теме
Generics нужны только для библиотек?
Нет. Они часто встречаются в обычном коде: API responses, React components, helper functions, collections
Почему не использовать any?
any стирает информацию о типе. Generic позволяет переиспользовать код и сохранить конкретный тип результата
Можно ли назвать T по-другому?
Да. T — традиция, а не правило. В прикладном коде часто лучше писать Item, Data, Entity
Когда стоит остановиться?
Если тип стал сложнее задачи и никто не понимает ошибку компилятора, возможно, generic слишком рано усложнил код. Начинай с простого типа и обобщай только при повторе
Что почитать дальше по TypeScript
- interface и type в TypeScript: когда что использовать — база для объектов, которые потом попадают в generic-типы.
- Union, literal types и enum в TypeScript — полезно для статусов внутри generic-ответов.
- TypeScript в React: props, state и события — там generics встречаются в хуках и компонентах.
- Ошибки TypeScript: any, unknown, never и strict — чтобы понимать, почему
anyне лучший выход.



