Обобщённые типы
До сих пор параметр типа был только у функций. Но у контейнера в порту рамка для этикетки есть с самого начала: контейнеры делают по одному чертежу, а груз и этикетку определяют потом. Типам нужно то же самое.
Представьте, что программа хранит значения в обёртке { value: … }: иногда число, иногда строку. Без параметра типа на каждый случай пришлось бы писать свой тип: type NumberBox = { value: number }, type StringBox = { value: string } и так далее. Форма одна, отличается только тип внутри — та же задача, что с функцией first.
Псевдоним с параметром типа
Обобщённый тип объявляет параметр типа в угловых скобках после своего имени, как функция:
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, и к нему можно прибавить единицу.
Обобщённый интерфейс
Параметр типа бывает и у интерфейса. Так часто описывают ответы сервера: обёртка одна, а данные в ней разные:
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); // annApiResponse<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.
Тип по умолчанию
Параметру типа можно дать значение по умолчанию — через знак =, как параметру функции:
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Разберём по строкам:
<T = string>говорит: если аргумент типа не написан,T— этоstring.- У
cityаргумента нет, и подсказка редактора покажет типField<string>. - У
yearаргумент написан, и значение по умолчанию не используется: типField<number>. - Программа печатает
Paris 2026.
Частая ошибка
Обобщённый тип использовали без аргумента:
type Box<T> = { value: T };
const box: Box = { value: 1 }; // ✗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>.