Skip to main content

Обмеження в дженериках

Що таке constraint

Constraint - це умова вигляду T extends ..., якою ви обмежуєте множину допустимих підстановок для параметра типу T. Мета - щоб у тілі функції/типу ви могли безпечно звертатися до властивостей/методів, які гарантовано є у T.

javascript
function len<T extends { length: number }>(x: T) { return x.length; // безпечно: у T є length }

Якщо переданий тип не задовольняє обмеження - буде помилка на етапі компіляції.


Основні форми обмежень

1) Структурні (shape/structural)

Обмежуємо "форму" типу: потрібні поля та їхні типи.

javascript
function save<T extends { id: string; name: string }>(entity: T) { /* ... */ }

Плюс: гнучко працює з "багатими" типами (надмножина полів допускається).


2) Обмеження за примітивами/об'єднаннями

Дозволяємо тільки задані базові типи або їхнє об'єднання.

javascript
function toStr<T extends string | number | boolean>(x: T): string { return String(x); }

3) "Тільки об'єкти"

Часто вживані варіанти:

javascript
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 + індексний доступ

Класичний патерн: ключ залежить від об'єкта, значення - від ключа.

javascript
function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }

5) Масиви/кортежі/readonly-масиви

javascript
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-типи

Обмежуємося функціями або класами.

javascript
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) Шаблонні рядкові типи

Обмежуємося рядками певного "формату".

javascript
type ID = `id_${number}`; function load<T extends ID>(id: T) { /* ... */ } load("id_42"); // добре load("user_42"); // Помилка

8) Виключення null/undefined

javascript
function ensure<T extends NonNullable<unknown>>(x: T) { // x не null і не undefined }

Як constraints впливають на виведення типів (inference)

  1. Уточнюють виведення: компілятор шукає найвужче значення в межах заданої множини.
javascript
function id<T extends string | number>(x: T): T { return x; } const a = id(42); // T = 42 (літеральний), потім звужується до number за потреби
  1. Дозволяють пов'язати параметри: без K extends keyof T компілятор не гарантує, що ключ валідний для T.
  2. Покращують автодоповнення: усередині тіла функції доступні методи/поля з обмеження.

Важливо: extends - це constraint у параметрах типу. Не плутати з extends в умовних типах (T extends U ? X : Y), там це перевірка підтипу для вибору гілки, а не обмеження параметра.


Комбінування та прийоми

Перетини для "мінімального набору полів"

javascript
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) {}

Обмеження + значення за замовчуванням

javascript
function pair<T, U extends string | number = string>(a: T, b?: U): [T, U] { return [a, b as U]; }

"Тільки ті ключі, що відповідають типу"

javascript
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 та автодоповнення, роблять узагальнений код реально типобезпечним.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.