- Писать обобщённую функцию с параметром типа
<T> - Описывать обобщённые интерфейсы и типы
- Ограничивать параметры типа через
extendsиkeyof - Применять служебные типы
Partial,Pick,Omit,RecordиReturnType
Нужна функция, которая возвращает первый элемент массива: для городов, для цен, для пользователей. Писать отдельные firstString, firstNumber и firstUser бессмысленно. А any уничтожает информацию о типе. Код ниже компилируется без ошибок, но при запуске падает с TypeError: city.toFixed is not a function — компилятор уже не знает, что city — строка:
function firstAny(items: any[]): any {
return items[0];
}
const city = firstAny(['Baku', 'Ganja']);
console.log(city.toFixed(2));Обобщённые функции
Функция, интерфейс или класс, принимающие тип в качестве параметра. Параметр типа пишут в угловых скобках — обычно <T>, — и при каждом использовании он заменяется конкретным типом, так же как обычный параметр заменяется конкретным значением.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const city = first(['Baku', 'Ganja']);
const score = first([90, 75]);
const empty = first<boolean>([]);
console.log(city?.toUpperCase(), score?.toFixed(1), empty);BAKU 90.0 undefined
city имеет тип string | undefined, а score — number | undefined. Массив может быть пустым, поэтому учтён и undefined.Здесь T означает «тип, который пока неизвестен». В вызове first(['Baku', 'Ganja']) TypeScript смотрит на аргумент и сам выводит, что T — это string. Тип можно указать и явно: first<boolean>([]). Это нужно, когда TypeScript не может определить тип сам, как в случае с пустым массивом.
Обобщённые интерфейсы
Параметр может принимать и сам тип. Сервер всегда отвечает в одинаковой форме — статус и данные, — но данные в каждом запросе разные. Вместо отдельного интерфейса на каждый случай пишем один обобщённый. На самом деле ты уже пользовался дженериками: Array<string> и Promise<number> — тоже обобщённые типы.
interface ApiResponse<T> {
status: number;
data: T;
}
type User = { id: number; name: string };
const userResponse: ApiResponse<User> = {
status: 200,
data: { id: 1, name: 'Leyla' },
};
const tagsResponse: ApiResponse<string[]> = {
status: 200,
data: ['typescript', 'generics'],
};
console.log(userResponse.data.name, tagsResponse.data.length);Leyla 2
Ограничения: extends и keyof
Иногда T не может быть «каким угодно типом». Функции, возвращающей более длинное из двух значений, нужно свойство length. Запись T extends { length: number } — это ограничение: T может быть только типом со свойством length — строкой, массивом и т. п. А у чисел length нет:
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
console.log(longest('Baku', 'Sumgait'));
console.log(longest([1, 2, 3], [4, 5]));
longest(10, 20);longest.ts:7:9 - error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
7 longest(10, 20);
~~Sumgait и [ 1, 2, 3 ].Оператор keyof T даёт объединение всех ключей объектного типа: для { title: string; year: number } это 'title' | 'year'. А T[K] — тип значения по этому ключу. Вместе они дают функцию, которая принимает только существующие свойства и возвращает точно нужный тип:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const book = { title: 'Ali and Nino', year: 1937 };
const title = getProperty(book, 'title');
const year = getProperty(book, 'year');
console.log(title.toUpperCase(), year + 1);
getProperty(book, 'author');book.ts:10:19 - error TS2345: Argument of type '"author"' is not assignable to parameter of type '"title" | "year"'.
10 getProperty(book, 'author');
~~~~~~~~title получает тип string, а year — number. Несуществующий ключ 'author' отклоняется.Служебные типы
В TypeScript есть готовые служебные типы (utility types), построенные на дженериках. Они создают новый тип из существующего, и одни и те же свойства не приходится писать дважды. Чаще всего используются эти:
| Тип | Что делает |
|---|---|
Partial<T> | делает все свойства необязательными |
Required<T> | делает все свойства обязательными |
Readonly<T> | делает все свойства доступными только для чтения |
Pick<T, K> | оставляет только выбранные свойства K |
Omit<T, K> | оставляет всё, кроме свойств K |
Record<K, V> | объект с ключами типа K и значениями типа V |
ReturnType<F> | тип, который возвращает функция F |
interface Product {
id: number;
name: string;
price: number;
stock: number;
}
const laptop: Product = { id: 1, name: 'Laptop', price: 1500, stock: 4 };
function update(product: Product, changes: Partial<Product>): Product {
return { ...product, ...changes };
}
type ProductCard = Pick<Product, 'name' | 'price'>;
type NewProduct = Omit<Product, 'id'>;
const card: ProductCard = { name: laptop.name, price: laptop.price };
const draft: NewProduct = { name: 'Mouse', price: 25, stock: 30 };
console.log(update(laptop, { price: 1350 }));
console.log(card, draft.name);{ id: 1, name: 'Laptop', price: 1350, stock: 4 }
{ name: 'Laptop', price: 1500 } MousePartial — для обновления (только изменённые свойства), Omit<Product, 'id'> — для нового товара (id назначает база данных), Pick — для маленькой карточки.type City = 'Baku' | 'Ganja' | 'Shaki';
const deliveryDays: Record<City, number> = {
Baku: 1,
Ganja: 2,
Shaki: 3,
};
function createUser(name: string, age: number) {
return { name, age, createdAt: '2025-09-01' };
}
type User = ReturnType<typeof createUser>;
const user: User = createUser('Elvin', 16);
console.log(deliveryDays.Ganja, user.name, user.createdAt);2 Elvin 2025-09-01
Record<City, number> требует значение для каждого города: забудешь Shaki — получишь ошибку TS2741.Главное
- Дженерик принимает тип как параметр:
function first<T>(items: T[]): T | undefined. Tобычно выводится из аргумента, а при необходимости указывается явно:first<boolean>([]).T extends Xограничивает параметр типа;K extends keyof Tразрешает только существующие ключи.Partial,Pick,Omit,RecordиReturnTypeсоздают новые типы из существующих.- Если параметр типа не связывает хотя бы два места, дженерик не нужен.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
<T> в записи function first<T>(items: T[]): T | undefined?