Перейти к содержимому
Теория · Дженерики · урок 7 из 10

Обобщённые типы

До сих пор параметр типа был только у функций. Но у контейнера в порту рамка для этикетки есть с самого начала: контейнеры делают по одному чертежу, а груз и этикетку определяют потом. Типам нужно то же самое.

Представьте, что программа хранит значения в обёртке { value: … }: иногда число, иногда строку. Без параметра типа на каждый случай пришлось бы писать свой тип: type NumberBox = { value: number }, type StringBox = { value: string } и так далее. Форма одна, отличается только тип внутри — та же задача, что с функцией first.

Псевдоним с параметром типа

Обобщённый тип объявляет параметр типа в угловых скобках после своего имени, как функция:

TypeScript
type Box<T> = { value: T };

const age: Box<number> = { value: 30 };
const nick: Box<string> = { value: "ann" };
console.log(age.value + 1, nick.value); // 31 ann

Разберём объявление:

  • type Box<T> объявляет псевдоним типа с параметром T;
  • { value: T } — форма объекта, где T стоит на месте типа свойства;
  • Box<number> — тип с аргументом: вместо T подставлен number, и получилась форма { value: number }.

У функции T выводится из аргументов вызова. У типа вызова нет, поэтому аргумент типа пишут сами: Box<number>, Box<string>. Программа печатает 31 ann: у age.value тип number, и к нему можно прибавить единицу.

Обобщённый интерфейс

Параметр типа бывает и у интерфейса. Так часто описывают ответы сервера: обёртка одна, а данные в ней разные:

TypeScript
interface User {
    id: number;
    nick: string;
}

interface ApiResponse<T> {
    ok: boolean;
    data: T;
}

const res: ApiResponse<User> = {
    ok: true,
    data: { id: 1, nick: "ann" },
};
console.log(res.data.nick); // ann

ApiResponse<User> — ответ, в поле data которого лежит User. Тот же интерфейс опишет и список: ApiResponse<User[]>.

Здесь аналогия с контейнером заканчивается. Настоящий контейнер не положишь в другой, а аргументом типа может быть другой обобщённый тип: Box<Box<number>> — коробка, в которой лежит коробка с числом.

Такую задачу часто дают на собеседовании: описать ответ сервера, у которого обёртка общая для всех запросов, а данные разные. Ожидаемое решение — обобщённый интерфейс вроде ApiResponse<T>.

Array<T> — тоже обобщённый тип

Теперь можно прочитать запись Array<number> до конца. В стандартной библиотеке TypeScript массив описан как interface Array<T>, и его методы говорят о T:

  • push(...items: T[]): number принимает элементы типа T;
  • pop(): T | undefined возвращает элемент типа T или undefined, если массив пуст;
  • length: number от T не зависит.

number[] — короткая запись того же Array<number>. В нём T — это number, поэтому у [1, 2].pop() тип number | undefined.

Тип по умолчанию

Параметру типа можно дать значение по умолчанию — через знак =, как параметру функции:

TypeScript
type Field<T = string> = { label: string; value: T };

const city: Field = { label: "City", value: "Paris" };
const year: Field<number> = { label: "Year", value: 2026 };
console.log(city.value, year.value); // Paris 2026

Разберём по строкам:

  1. <T = string> говорит: если аргумент типа не написан, T — это string.
  2. У city аргумента нет, и подсказка редактора покажет тип Field<string>.
  3. У year аргумент написан, и значение по умолчанию не используется: тип Field<number>.
  4. Программа печатает Paris 2026.

Частая ошибка

Обобщённый тип использовали без аргумента:

TypeScript
type Box<T> = { value: T };

const box: Box = { value: 1 }; // ✗
TEXT
error TS2314: Generic type 'Box' requires 1 type argument(s).

Сообщение говорит: обобщённому типу Box нужен один аргумент типа. Это как отправить контейнер с пустой рамкой: непонятно, с каким грузом его сверять. Выводить T из значения { value: 1 } TypeScript не станет, ведь у типа нет вызова. Напишите аргумент — Box<number> — или дайте параметру значение по умолчанию: type Box<T = number>.

Главное

  • Обобщённый тип объявляет параметр в угловых скобках: type Box<T> = { value: T } или interface ApiResponse<T>.
  • У типа T не выводится, аргумент пишут сами: Box<number>.
  • Array<T> — обобщённый интерфейс стандартной библиотеки, а number[] — короткая запись Array<number>.
  • Значение по умолчанию <T = string> позволяет не писать аргумент: Field значит Field<string>.