Ты наверняка видел такой код:
function getData(data: any) {
return data;
}Работает? Работает.
Красиво? Ну… пока не начались баги. 😅
any — это как сказать TypeScript:
«Брат, я сам разберусь».
А потом оказывается, что ты сам вообще не разобрался.
И вот тут появляются Generics — одна из самых мощных фишек TypeScript. Они позволяют писать функции, которые подходят для разных типов, но при этом не теряют информацию о том, с каким именно типом работают.
Звучит сложно? На самом деле Generics — одна из самых логичных вещей в TypeScript. Давай разберёмся простыми словами. 👇

🤔 Представь универсальную коробку
Допустим, у тебя есть волшебная коробка. Ты можешь положить туда яблоко, телефон, ноутбук или геймпад.
Но главное — коробка всегда возвращает именно то, что ты туда положил.
Не «какой-то предмет».
Не «что угодно».
А конкретно яблоко, телефон или геймпад.
Вот примерно этим и занимаются Generics: они позволяют коду работать с разными типами и при этом помнить каждый конкретный тип.
😭 Как обычно делают без Generics
Новичок может написать такую функцию:
function identity(data: any) {
return data;
}А потом вызвать её:
const userName = identity("Alex");Проблема в том, что TypeScript видит результат как any.
Подсказки становятся бесполезными, автодополнение начинает гадать, а проверка типов уходит пить кофе.
Ты можешь написать:
userName.toFixed(2);Хотя userName на самом деле строка.
TypeScript промолчит, потому что ты сам разрешил ему ничего не проверять.
any в этот момент выглядит примерно так:
«Я предупреждал? Нет. А должен был? Тоже нет».
😎 Та же функция с Generics
Теперь напишем её правильно:
function identity<T>(data: T): T {
return data;
}Вот эта загадочная конструкция:
<T>означает:
«Конкретный тип станет известен, когда функцию вызовут».
Например:
const userName = identity("Alex");
const age = identity(25);
const isAdmin = identity(true);TypeScript автоматически понимает:
const userName: string;
const age: number;
const isAdmin: boolean;Одна функция работает с разными типами, но каждый раз возвращает именно тот тип, который получила.
Функция универсальная, а типизация остаётся строгой. Красота. ✨
🧠 Что вообще такое T?
Многие пугаются буквы T, будто сейчас начнётся высшая математика, компиляторы и ритуал вызова senior-разработчика.
Но T — это просто переменная для типа.
Обычная переменная хранит значение:
const count = 5;А generic-параметр хранит тип:
<T>Название может быть почти любым:
function identity<Type>(data: Type): Type {
return data;
}Но чаще используют короткие обозначения:
T— Type;K— Key;V— Value;E— Element.
Это не обязательные правила, а распространённые соглашения.
🔥 Generics на примере массива
Допустим, нам нужна функция, которая возвращает первый элемент массива.
Без Generics она может выглядеть так:
function first(arr: any[]) {
return arr[0];
}Используем:
const firstUser = first(users);И TypeScript снова отвечает:
«Ну… там что-то есть. Наверное».
Результат имеет тип any. Мы потеряли информацию о том, какие элементы лежали в массиве.
Теперь версия с Generics:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}Теперь TypeScript понимает тип результата:
const firstNumber = first([10, 20, 30]);
// number | undefined
const firstName = first(["Alex", "Max", "Kate"]);
// string | undefinedМы добавили undefined, потому что массив может оказаться пустым. Типизация не только умная, но ещё и честная.
🎯 Несколько generic-параметров
Функция может использовать не один, а несколько типов.
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}Использование:
const user = createPair("Alex", 25);TypeScript определит результат как:
[string, number]Первое значение может быть строкой, второе — числом. А при другом вызове типы будут уже другими.
const settings = createPair(true, {
theme: "dark"
});Функция одна, комбинаций типов — сколько угодно.
📦 Generics в интерфейсах
Generics нужны не только функциям. Их часто используют в интерфейсах и типах.
Например, ответ API:
interface ApiResponse<T> {
data: T;
success: boolean;
error?: string;
}Теперь один интерфейс можно применять к разным данным:
interface User {
id: number;
name: string;
}
interface Product {
id: number;
title: string;
price: number;
}
const userResponse: ApiResponse = {
data: {
id: 1,
name: "Alex"
},
success: true
};
const productResponse: ApiResponse = {
data: [
{
id: 1,
title: "Keyboard",
price: 5000
}
],
success: true
};Без Generics пришлось бы создавать отдельные интерфейсы:
UserApiResponse
ProductApiResponse
OrderApiResponse
ArticleApiResponse
CatApiResponse
DogApiResponseЧерез неделю проект выглядел бы как каталог существ в игре.
Generics говорят:
«Спокойно. Одной структуры достаточно».
🔒 Ограничения для Generics
Иногда функция не должна принимать вообще любой тип.
Например, мы хотим получить длину значения:
function getLength<T>(value: T) {
return value.length;
}TypeScript возмутится, и будет прав.
Не у каждого типа есть свойство length. У строки есть, у массива есть, а у числа — нет.
Можно добавить ограничение:
interface HasLength {
length: number;
}
function getLength(value: T): number {
return value.length;
}Теперь функция принимает только значения, у которых действительно есть length.
getLength("TypeScript");
getLength([1, 2, 3]);
getLength({ length: 10 });
getLength(42);
// Ошибкаextends здесь означает не наследование в привычном смысле, а ограничение:
«Тип T должен соответствовать указанной структуре».
🔑 Generics и keyof
А теперь функция становится ещё умнее.
Допустим, мы хотим безопасно получать значение свойства объекта:
function getProperty<T, K extends keyof T>(
object: T,
key: K
): T[K] {
return object[key];
}Использование:
const user = {
id: 1,
name: "Alex",
isAdmin: true
};
const userName = getProperty(user, "name");
const userId = getProperty(user, "id");TypeScript понимает, что:
userName // string
userId // numberА несуществующее свойство использовать не получится:
getProperty(user, "email");
// ОшибкаВот здесь Generics уже реально создают ощущение, что функция получила повышение и теперь работает тимлидом.
⚙️ Когда Generics действительно нужны?
Generics полезны, когда между входными и выходными типами существует связь.
Например:
функция возвращает тот же тип, который получила;
функция работает с массивом элементов неизвестного заранее типа;
API-ответ содержит разные виды данных;
компонент принимает данные определённого типа;
класс должен работать с разными сущностями;
нужно сохранить строгую типизацию без копирования кода.
Главный вопрос:
«Нужно ли TypeScript запомнить конкретный тип и использовать его дальше?»
Если да — вероятно, тебе нужен Generic.
🚫 Когда Generics не нужны?
Иногда разработчики узнают про Generics и начинают добавлять <T> вообще везде.
Например:
function sum<T>(a: number, b: number): number {
return a + b;
}Здесь T вообще нигде не используется. Он сидит в функции как случайный человек на созвоне и сам не понимает, зачем его пригласили.
Правильнее:
function sum(a: number, b: number): number {
return a + b;
}Generics нужны не для того, чтобы код выглядел сложнее. Они нужны, чтобы связывать типы и делать код переиспользуемым.
📚 Ты уже давно используешь Generics
Даже если ты раньше не писал собственные Generics, ты почти наверняка уже ими пользовался.
Array<string>
Promise<User>
Map<number, User>
Set<string>Обычная запись массива:
string[]по сути является более короткой версией:
Array<string>А когда пишешь:
Promise<User>ты сообщаешь:
«Этот Promise когда-нибудь вернёт объект User. Возможно, когда бэкенд закончит думать».
🧩 Generics в реальной разработке
Они активно используются в React, Vue, Angular, NestJS, Prisma, Axios, Redux, TanStack Query и огромном количестве других библиотек.
Например, запрос данных:
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json() as Promise<T>;
}Использование:
const user = await fetchData<User>("/api/user/1");Теперь TypeScript знает структуру результата и подсказывает поля объекта.
Но есть важный момент: Generic не проверяет реальные данные, которые пришли с сервера. Он помогает описать ожидаемый тип на этапе разработки.
Если сервер вместо пользователя пришлёт мем с котом, TypeScript в браузере не выбежит и не остановит его.
Для проверки данных во время выполнения нужны дополнительные инструменты, например схемы валидации.
🚀 Как научиться использовать Generics уверенно?
Просто прочитать определение недостаточно. Generics начинают нормально укладываться в голове, когда ты самостоятельно пишешь универсальные функции, работаешь с массивами, объектами, API и типами результатов.
В приложении Кодик обучение программированию строится вокруг практики. Ты изучаешь TypeScript и другие технологии, выполняешь задания, пишешь код и сразу получаешь проверку решений.
Так Generics перестают быть странной конструкцией из документации и становятся обычным рабочим инструментом.
А ещё у Кодика есть сообщество в Telegram-канале. Там выходят полезные посты, мини-разборы, задачи, новости разработки и материалы для повторения изученных тем.
Это удобный способ продолжать обучение программированию даже тогда, когда на полноценный урок есть только несколько минут. 📱
💡 Итог
Generics — это способ сказать TypeScript:
«Мой код может работать с разными типами, но ты должен запомнить конкретный тип и продолжать его проверять».
Они помогают:
избавляться от
any;сохранять автодополнение;
писать универсальные функции;
создавать переиспользуемые интерфейсы;
уменьшать количество копипаста;
безопасно работать с массивами, объектами и API.
И главное: Generics не делают функцию магической.
Они просто помогают ей не забывать, с каким типом она работает.
Но со стороны действительно выглядит так, будто функция стала немного умнее. 😎
