{}const=>[]async()letfn</>var
РазработкаJSОсновыОбзор

Generics в TypeScript: когда функция становится умнее

Разбираемся, что такое Generics в TypeScript, почему они намного лучше any, как работают <T> и почему без них невозможно представить современные библиотеки и крупные проекты.

К

Кодик

Автор

7 мин чтения

Ты наверняка видел такой код:

function getData(data: any) {
  return data;
}

Работает? Работает.

Красиво? Ну… пока не начались баги. 😅

any — это как сказать TypeScript:

«Брат, я сам разберусь».

А потом оказывается, что ты сам вообще не разобрался.

И вот тут появляются Generics — одна из самых мощных фишек TypeScript. Они позволяют писать функции, которые подходят для разных типов, но при этом не теряют информацию о том, с каким именно типом работают.

Звучит сложно? На самом деле Generics — одна из самых логичных вещей в TypeScript. Давай разберёмся простыми словами. 👇

🤔 Представь универсальную коробку

Допустим, у тебя есть волшебная коробка. Ты можешь положить туда яблоко, телефон, ноутбук или геймпад.

Но главное — коробка всегда возвращает именно то, что ты туда положил.

Не «какой-то предмет».

Не «что угодно».

А конкретно яблоко, телефон или геймпад.

Вот примерно этим и занимаются Generics: они позволяют коду работать с разными типами и при этом помнить каждый конкретный тип.

🔥 100 000+ учеников уже с нами

Устал читать теорию?
Пора кодить!

Кодик — приложение, где ты учишься программировать через практику. AI-наставник, интерактивные уроки, реальные проекты.

🤖 AI 24/7
🎓 Сертификаты
💰 Бесплатно
🚀 Начать учиться
Присоединились сегодня

😭 Как обычно делают без 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 не делают функцию магической.

Они просто помогают ей не забывать, с каким типом она работает.

Но со стороны действительно выглядит так, будто функция стала немного умнее. 😎

🎯Хватит откладывать

Понравилась статья?
Пора применять на практике!

В Кодик ты не просто читаешь — ты сразу пишешь код. Теория + практика = реальный скилл.

Мгновенная практика
🧠AI объяснит код
🏆Сертификат

Без регистрации • Без карты