Обобщённые типы (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 и т.д.) выбираются по смыслу или соглашению.