TYPESCRIPT
#typescript#generics#functions#typing#reusability

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 выведет типы автоматически в большинстве случаев.