Обмеження в дженериках
Що таке constraint
Constraint - це умова вигляду T extends ..., якою ви обмежуєте множину допустимих підстановок для параметра типу T.
Мета - щоб у тілі функції/типу ви могли безпечно звертатися до властивостей/методів, які гарантовано є у T.
function len<T extends { length: number }>(x: T) {
return x.length; // безпечно: у T є length
}Якщо переданий тип не задовольняє обмеження - буде помилка на етапі компіляції.
Основні форми обмежень
1) Структурні (shape/structural)
Обмежуємо "форму" типу: потрібні поля та їхні типи.
function save<T extends { id: string; name: string }>(entity: T) { /* ... */ }Плюс: гнучко працює з "багатими" типами (надмножина полів допускається).
2) Обмеження за примітивами/об'єднаннями
Дозволяємо тільки задані базові типи або їхнє об'єднання.
function toStr<T extends string | number | boolean>(x: T): string {
return String(x);
}3) "Тільки об'єкти"
Часто вживані варіанти:
function f1<T extends object>(x: T) {} // виключає примітиви
function f2<T extends Record<string, unknown>>(x: T) {}// ключі: string, значення: unknown
function f3<T extends Record<PropertyKey, unknown>>(x: T) {} // допускає string|number|symbol ключі
object- широкий, але виключаєnull,undefinedі примітиви.Record- точніше задає форму словника.
4) Залежні параметри: keyof + індексний доступ
Класичний патерн: ключ залежить від об'єкта, значення - від ключа.
function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}5) Масиви/кортежі/readonly-масиви
function first<T extends readonly unknown[]>(arr: T) {
return arr[0]; // тип - T[number] / елемент кортежу
}
const a = first([1, 2, 3] as const); // 1 | 2 | 3
readonly unknown[]запобігає мутації;T extends any[]- мутабельний масив.
6) Callable/constructable-типи
Обмежуємося функціями або класами.
type AnyFn = (...args: any[]) => any;
function wrap<T extends AnyFn>(fn: T) { /* ... */ }
type Ctor<A extends any[] = any[], R = any> = new (...args: A) => R;
function make<T extends Ctor>(C: T): InstanceType<T> {
return new C();
}7) Шаблонні рядкові типи
Обмежуємося рядками певного "формату".
type ID = `id_${number}`;
function load<T extends ID>(id: T) { /* ... */ }
load("id_42"); // добре
load("user_42"); // Помилка8) Виключення null/undefined
function ensure<T extends NonNullable<unknown>>(x: T) {
// x не null і не undefined
}Як constraints впливають на виведення типів (inference)
- Уточнюють виведення: компілятор шукає найвужче значення в межах заданої множини.
function id<T extends string | number>(x: T): T { return x; }
const a = id(42); // T = 42 (літеральний), потім звужується до number за потреби- Дозволяють пов'язати параметри: без
K extends keyof Tкомпілятор не гарантує, що ключ валідний дляT. - Покращують автодоповнення: усередині тіла функції доступні методи/поля з обмеження.
Важливо:
extends- це constraint у параметрах типу. Не плутати зextendsв умовних типах (T extends U ? X : Y), там це перевірка підтипу для вибору гілки, а не обмеження параметра.
Комбінування та прийоми
Перетини для "мінімального набору полів"
type AtLeast<T, K extends keyof T> = Pick<T, K> & Partial<T>;
function useUser<U extends AtLeast<{ id: string; name: string; age: number }, "id" | "name">>(u: U) {}Обмеження + значення за замовчуванням
function pair<T, U extends string | number = string>(a: T, b?: U): [T, U] {
return [a, b as U];
}"Тільки ті ключі, що відповідають типу"
type KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
function pickByType<T extends object, V>(obj: T, key: KeysOfType<T, V>): V {
return obj[key] as V;
}Часті помилки та пастки
- Занадто широкий
object: не дає гарантій щодо значень/ключів. Для словників кращеRecord<PropertyKey, unknown>. - Забули
keyof-зв'язку:function g<T, K>(obj: T, key: K)- ключ не прив'язаний доT. - Обмеження занадто вузьке: ламає inference (TS перестає виводити літеральні типи). Розширюйте до об'єднань/шаблонів.
- Не можна звузити в тілі: якщо
T extends { x: number }, ви можете використовуватиx, але не можете "звузитиTдо конкретного варіанта" без додаткових перевірок/умовних типів.
Короткий підсумок
- Використовуйте
T extends ..., щоб контролювати допустимі підстановки і отримати гарантії всередині реалізації. - Основні патерни: структурний shape, об'єднання примітивів, словники (
Record), залежні ключі (K extends keyof T), масиви/кортежі/readonly, функції/конструктори, шаблонні рядки. - Constraints покращують inference та автодоповнення, роблять узагальнений код реально типобезпечним.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.