Ограничения дженериков: extends
Ограничения generic-параметров через
extendsпозволяют указать, что типTдолжен быть совместим с определённым типом, давая доступ к свойствам этого типа внутри generic-функции или класса.
Зачем нужно
Без ограничений TypeScript трактует T как unknown — нельзя обратиться ни к каким свойствам. T extends SomeType даёт компилятору гарантию что T имеет нужные поля, позволяя типобезопасно их использовать.
Где используется
- Функции, работающие с объектами определённой формы:
T extends { id: string } - Generic с ключами:
K extends keyof T - Ограничение на конструктор:
T extends new (...args) => any - Conditional types:
T extends string ? ... : ...
Основной контент
Базовое ограничение
// Без ограничения — нельзя обратиться к свойствам
function identity<T>(x: T): T {
// x.length; // Error: Property 'length' does not exist on type 'T'
return x;
}
// С ограничением — гарантировано наличие length
function logLength<T extends { length: number }>(x: T): T {
console.log(x.length); // OK
return x;
}
logLength("hello"); // OK — string имеет length
logLength([1, 2, 3]); // OK — array имеет length
logLength({ length: 5 }); // OK
// logLength(42); // Error — number не имеет length
K extends keyof T
Самое распространённое ограничение — безопасный доступ к полю объекта по ключу.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30, email: "a@b.com" };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, "role"); // Error — нет такого ключа
// Установить значение
function setProperty<T, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): void {
obj[key] = value;
}
setProperty(user, "age", 31); // OK
// setProperty(user, "age", "old"); // Error — age: number, не string
Ограничение на интерфейс
interface HasId {
id: string;
}
interface HasTimestamps {
createdAt: Date;
updatedAt: Date;
}
// T должен иметь id
function findById<T extends HasId>(
items: T,
id: string
): T | undefined {
return items.find(item => item.id === id);
}
// T должен иметь id И timestamps
function updateTimestamp<T extends HasId & HasTimestamps>(
entity: T
): T {
return { ...entity, updatedAt: new Date };
}
Ограничение на конструктор
// T должен быть классом (конструктором)
function createInstance<T>(
Constructor: new (...args: any) => T
): T {
return new Constructor();
}
class Service {}
const service = createInstance(Service); // Service
// Mixin-паттерн
type Constructor<T = {}> = new (...args: any) => T;
function Serializable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
serialize: string {
return JSON.stringify(this);
}
};
}
Условные ограничения
// extends в conditional types — другое использование
type IsArray<T> = T extends any ? true : false;
type A = IsArray<string>; // true
type B = IsArray<string>; // false
// Ограничение для conditional type
type NonNullableKeys<T> = {
[K in keyof T]: null extends T[K] ? never : K;
}[keyof T];
interface User {
id: string;
name: string;
email: string | null;
}
type RequiredKeys = NonNullableKeys<User>; // "id" | "name"
Несколько ограничений
// Пересечение через &
function process<T extends HasId & { name: string }>(
entity: T
): string {
return `${entity.id}: ${entity.name}`;
}
// Несколько независимых параметров с ограничениями
function transform<T extends object, K extends keyof T>(
obj: T,
keys: K
): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(k => (result[k] = obj[k]));
return result;
}
Частые ошибки
T extends any— то же чтоTбез ограничения; это не "принять любой тип".- Ограничить слишком жёстко —
T extends UserвместоT extends { id: string }— лишняя специализация. - Путать
extendsв generic и в conditional —<T extends X>ограничивает generic;T extends X ? A : B— conditional type. - Не использовать
K extends keyof Tдля типобезопасного доступа к полям — без него TypeScript не знает что ключ существует.
Связанные темы
- Generic функции
- Generic интерфейсы и классы
- Дженерики -- практические примеры
- Conditional types
- _MOC TypeScript