Ограничения (extends)
extends ограничивает возможные типы параметра. Без него T — что угодно. С ним — только подходящее.
Базовый пример
function logLength<T extends { length: number }>(value: T): T {
console.log(value.length);
return value;
}
logLength('hello'); // ок: string имеет length
logLength([1, 2, 3]); // ок: массив имеет length
logLength(42); // ошибка: number не имеет length
Ограничение на примитив
function toString<T extends string | number>(value: T): string {
return String(value);
}
Ограничение на объект
function getKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
object — любой объект, но не примитив.
Ограничение через интерфейс
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id);
}
const users = [{ id: 1, name: 'Алиса' }];
findById(users, 1);
T обязан иметь id: number.
keyof
Ограничение по ключам объекта:
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Алиса', age: 25 };
const name = getValue(user, 'name'); // string
const age = getValue(user, 'age'); // number
getValue(user, 'email'); // ошибка
K extends keyof T— K является ключом TT[K]— тип значения по ключу
Ограничение на union ключей
type UserKeys = 'name' | 'email';
function getUserField<T extends { [K in UserKeys]: string }>(obj: T, key: UserKeys): string {
return obj[key];
}
Несколько ограничений
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
Ограничение с дефолтом
type Container<T extends object = {}> = {
items: T;
};
Ограничение на класс
class Animal {
name = '';
}
function getName<T extends Animal>(animal: T): string {
return animal.name;
}
T — наследник Animal.
Самоссылка
type Comparable<T> = {
compareTo(other: T): number;
};
function max<T extends Comparable<T>>(a: T, b: T): T {
return a.compareTo(b) > 0 ? a : b;
}
Практика: pluck
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: 'Алиса', age: 25 },
{ name: 'Боб', age: 30 },
];
pluck(users, 'name'); // string[]
pluck(users, 'age'); // number[]
Практика: groupBy
function groupBy<T, K extends keyof T>(items: T[], key: K): Record<string, T[]> {
return items.reduce((acc, item) => {
const group = String(item[key]);
acc[group] = acc[group] ?? [];
acc[group].push(item);
return acc;
}, {} as Record<string, T[]>);
}
Итоги
extendsограничивает параметрkeyof— для ключейT[K]— тип значения по ключу- Несколько ограничений — через пересечение
- Ограничения делают код безопаснее