Параметр типу може бути обмежений іншим параметром - так описують зв'язок між аргументами функції.
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ id: 1, name: 'Оля', age: 28 },
{ id: 2, name: 'Іра', age: 31 },
];
pluck(users, 'name'); // string[]
pluck(users, 'age'); // number[]
pluck(users, 'email'); // помилка: 'email' немає серед ключів
Як це працює:
- TypeScript виводить
Tз першого аргументу - тип елементів масиву; K extends keyof Tдозволяє другим аргументом лише ключіT;Kвиводиться як конкретний літерал ('name'), а не весьkeyof T, тож тип поверненняT[K][]- точний.
Інші приклади зв'язаних параметрів:
// значення має відповідати типу поля
function setField<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
setField(user, 'age', 30); // ок
setField(user, 'age', '30'); // помилка: string не number
// групування
function groupBy<T, K extends keyof T>(items: T[], key: K): Map<T[K], T[]> {
const groups = new Map<T[K], T[]>();
for (const item of items) {
const group = groups.get(item[key]) ?? [];
group.push(item);
groups.set(item[key], group);
}
return groups;
}
Типові помилки:
key: keyof Tбез окремого параметра:function getField<T>(obj: T, key: keyof T): T[keyof T]- тип повернення стає об'єднанням типів усіх полів. ОкремийKзберігає зв'язок з конкретним ключем;K extends stringзамістьkeyof T- тоді немає перевірки, що ключ існує;- символьні й числові ключі:
keyof Tможе міститиnumber | symbol. Якщо ключ використовується в рядкових операціях (шаблонні рядки), -K extends keyof T & stringабоExtract<keyof T, string>.
Цей прийом - основа багатьох бібліотек: типізовані таблиці (columnHelper.accessor('email')), форми (register('name') у React Hook Form), сховища стану з селекторами за ключем.
Докладніше в документації: Generics: параметри типу в обмеженнях