TypeScript: дженерики для переиспользуемых функций
Использование дженериков для создания универсальных функций, работающих с разными типами данных без потери типизации.
Дженерики позволяют создавать функции, которые работают с разными типами, сохраняя при этом полную типизацию.
Базовая функция с дженериком
// Функция, которая работает с любым массивом
function getFirst<T>(items: T[]): T | undefined {
return items[0];
}
// Использование с разными типами
const numbers = [1, 2, 3];
const firstNumber = getFirst(numbers); // number | undefined
const strings = ["a", "b", "c"];
const firstString = getFirst(strings); // string | undefined
Работа с объектами
interface Product {
id: number;
name: string;
}
const products: Product[] = [
{ id: 1, name: "Laptop" },
{ id: 2, name: "Mouse" }
];
const firstProduct = getFirst(products); // Product | undefined
if (firstProduct) {
console.log(firstProduct.name); // Автодополнение работает
}
Функция для поиска по условию
function findItem<T>(
items: T[],
predicate: (item: T) => boolean
): T | undefined {
return items.find(predicate);
}
// Использование
const users = [
{ id: 1, name: "John", age: 30 },
{ id: 2, name: "Jane", age: 25 }
];
const user = findItem(users, u => u.age > 25);
// user имеет тип { id: number; name: string; age: number } | undefined
Функция для преобразования массива
function mapItems<T, R>(
items: T[],
mapper: (item: T) => R
): R[] {
return items.map(mapper);
}
// Использование
const numbers = [1, 2, 3];
const strings = mapItems(numbers, n => n.toString()); // string[]
const products = [
{ id: 1, name: "Laptop", price: 1000 },
{ id: 2, name: "Mouse", price: 20 }
];
const names = mapItems(products, p => p.name); // string[]
const prices = mapItems(products, p => p.price); // number[]
Функция с ограничением типа
// Ограничиваем T только объектами с полем id
function getById<T extends { id: number }>(
items: T[],
id: number
): T | undefined {
return items.find(item => item.id === id);
}
// Работает с любым объектом, у которого есть id
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" }
];
const user = getById(users, 1); // { id: number; name: string } | undefined
Функция для группировки
function groupBy<T, K extends string | number>(
items: T[],
keyFn: (item: T) => K
): Record<K, T[]> {
return items.reduce((acc, item) => {
const key = keyFn(item);
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {} as Record<K, T[]>);
}
// Использование
const products = [
{ id: 1, name: "Laptop", category: "electronics" },
{ id: 2, name: "Mouse", category: "electronics" },
{ id: 3, name: "Desk", category: "furniture" }
];
const grouped = groupBy(products, p => p.category);
// Record<"electronics" | "furniture", Product[]>
Функция для создания уникальных значений
function unique<T>(items: T[]): T[] {
return Array.from(new Set(items));
}
// Работает с любыми типами
const numbers = unique([1, 2, 2, 3, 3]); // [1, 2, 3]
const strings = unique(["a", "b", "a"]); // ["a", "b"]
Usage:
Используй дженерики когда функция должна работать с разными типами, но сохранять типизацию. Это избавляет от дублирования кода и обеспечивает типобезопасность.
Notes:
⚠️ Дженерики не усложняют код, а делают его более гибким. Используй ограничения (extends) когда нужно гарантировать наличие определённых свойств. TypeScript выведет типы автоматически в большинстве случаев.