Практика: типобезопасные утилиты
Собрали функции, где дженерики дают реальную пользу.
pick
function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach((key) => {
result[key] = obj[key];
});
return result;
}
const user = { name: 'Алиса', age: 25, email: 'a@b.com' };
const preview = pick(user, ['name', 'email']);
// { name: string; email: string }
omit
function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const result = { ...obj };
keys.forEach((key) => delete result[key]);
return result;
}
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[]>);
}
sortBy
function sortBy<T, K extends keyof T>(items: T[], key: K, direction: 'asc' | 'desc' = 'asc'): T[] {
return [...items].sort((a, b) => {
const av = a[key];
const bv = b[key];
if (av < bv) return direction === 'asc' ? -1 : 1;
if (av > bv) return direction === 'asc' ? 1 : -1;
return 0;
});
}
uniqueBy
function uniqueBy<T, K extends keyof T>(items: T[], key: K): T[] {
const seen = new Set<T[K]>();
return items.filter((item) => {
if (seen.has(item[key])) return false;
seen.add(item[key]);
return true;
});
}
indexBy
function indexBy<T, K extends keyof T>(items: T[], key: K): Record<string, T> {
return items.reduce((acc, item) => {
acc[String(item[key])] = item;
return acc;
}, {} as Record<string, T>);
}
mapValues
function mapValues<T extends object, R>(
obj: T,
fn: (value: T[keyof T]) => R
): Record<keyof T, R> {
const result = {} as Record<keyof T, R>;
(Object.keys(obj) as (keyof T)[]).forEach((key) => {
result[key] = fn(obj[key]);
});
return result;
}
chunk
function chunk<T>(items: T[], size: number): T[][] {
const result: T[][] = [];
for (let i = 0; i < items.length; i += size) {
result.push(items.slice(i, i + size));
}
return result;
}
invoke
Типобезопасный вызов метода:
function invoke<T, K extends keyof T>(
obj: T,
key: K
): T[K] extends (...args: any[]) => infer R ? R : never {
const fn = obj[key];
return typeof fn === 'function' ? fn.call(obj) : (undefined as never);
}
Создание API-клиента
type ApiRoutes = {
'/users': { get: () => User[]; post: (body: UserInput) => User };
'/users/:id': { get: (id: number) => User };
};
function createClient<R extends Record<string, Record<string, (...args: any[]) => any>>>() {
return function call<K extends keyof R, M extends keyof R[K]>(
route: K,
method: M,
...args: Parameters<R[K][M]>
): ReturnType<R[K][M]> {
// ...
};
}
Итоги
pick,omit,groupBy,sortByuniqueBy,indexBy,mapValues,chunk- Дженерики сохраняют точные типы
keyofзащищает от опечаток- Такие утилиты удобно выносить в
lib/