← Назад к вопросам

Сталкивался ли с дженериками

1.7 Middle🔥 201 комментариев
#TypeScript#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Generics в TypeScript: глубокий опыт

Что такое Generics

Generics (обобщённые типы) — это способ написать код, который работает с любым типом, но сохраняет type safety. Это одна из самых мощных фич TypeScript.

Простой пример:

// БЕЗ generics — теряем type информацию
function getFirstElement(arr: any[]): any {
  return arr[0];
}

const first = getFirstElement([1, 2, 3]);
// IDE не знает, что first это number!

// С generics — type safety сохранён
function getFirstElement<T>(arr: T[]): T {
  return arr[0];
}

const first = getFirstElement([1, 2, 3]); // TypeScript знает, что это number
const name = getFirstElement(['Alice', 'Bob']); // TypeScript знает, что это string

1. Generic Functions

Базовые примеры:

// Simple identity function
function identity<T>(value: T): T {
  return value;
}

identity<number>(42); // OK
identity<string>('hello'); // OK
identity('hello'); // TypeScript infers тип автоматически

// Multiple type parameters
function swap<A, B>(tuple: [A, B]): [B, A] {
  return [tuple[1], tuple[0]];
}

swap<number, string>([1, 'hello']); // [string, number]

// С constraints (ограничениями)
function merge<T extends object>(obj1: T, obj2: T): T {
  return { ...obj1, ...obj2 };
}

merge({ name: 'John' }, { age: 30 }); // OK
merge(123, 456); // ОШИБКА! number не extends object

2. Generic Interfaces

Использую часто в API responses:

// Generic interface для API ответов
interface ApiResponse<T> {
  data: T;
  error: string | null;
  timestamp: Date;
  status: 'success' | 'error';
}

// Использование
interface User {
  id: number;
  name: string;
  email: string;
}

const getUserResponse: ApiResponse<User> = {
  data: { id: 1, name: 'John', email: 'john@example.com' },
  error: null,
  timestamp: new Date(),
  status: 'success'
};

// Для списка пользователей
const listUsersResponse: ApiResponse<User[]> = {
  data: [{...}, {...}],
  error: null,
  timestamp: new Date(),
  status: 'success'
};

3. Generic Classes

Пример: Generic Repository

// В production используем это каждый день

interface Entity {
  id: number;
  createdAt: Date;
}

class Repository<T extends Entity> {
  private items: T[] = [];

  async find(id: number): Promise<T | null> {
    return this.items.find(item => item.id === id) || null;
  }

  async findAll(): Promise<T[]> {
    return this.items;
  }

  async create(item: T): Promise<T> {
    this.items.push(item);
    return item;
  }

  async update(id: number, updates: Partial<T>): Promise<T> {
    const item = await this.find(id);
    if (!item) throw new Error('Not found');
    Object.assign(item, updates);
    return item;
  }
}

// Использование
interface User extends Entity {
  name: string;
  email: string;
}

const userRepository = new Repository<User>();
await userRepository.create({
  id: 1,
  name: 'John',
  email: 'john@example.com',
  createdAt: new Date()
});

// IDE знает все методы и типы!
const user = await userRepository.find(1);
console.log(user.name); // OK
console.log(user.xyz); // ОШИБКА! нет такого свойства

4. Conditional Types

Очень мощная фишка:

// Условные типы
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<number>; // false

// Более практичный пример:
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number

// Реальный пример из product кода:
type ApiReturnType<T> = T extends Promise<infer U>
  ? U
  : T extends (...args: any[]) => infer R
    ? R
    : T;

async function getUser(id: number): Promise<User> {
  return { id, name: 'John', email: 'john@example.com', createdAt: new Date() };
}

type UserType = ApiReturnType<typeof getUser>; // User

5. Generic Utility Types

TypeScript встроенные утилиты:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// Partial<T> — все поля опциональны
type UpdateUserInput = Partial<User>; // все поля опциональны
const update: UpdateUserInput = { name: 'Jane' }; // OK

// Required<T> — все поля обязательны
type CompleteUser = Required<User>; // все поля обязательны

// Readonly<T> — все поля readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {...};
user.name = 'Jane'; // ОШИБКА!

// Pick<T, K> — выбираем только нужные поля
type UserPreview = Pick<User, 'id' | 'name'>;
const preview: UserPreview = { id: 1, name: 'John' }; // OK
const preview: UserPreview = { id: 1, name: 'John', email: 'john@example.com' }; // ОШИБКА!

// Omit<T, K> — исключаем поля
type UserPublic = Omit<User, 'password'>;
// Имеет: id, name, email (password исключён)

// Record<K, T> — объект с конкретными ключами
type RolePermissions = Record<'admin' | 'user' | 'guest', string[]>;
const permissions: RolePermissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read']
};

6. Generic Constraints

Ограничение типов:

// Constraint: T должен быть объектом с свойством 'length'
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength('hello'); // OK (string имеет length)
getLength([1, 2, 3]); // OK (array имеет length)
getLength({ length: 5 }); // OK
getLength(123); // ОШИБКА! number не имеет length

// Constraint на ключи объекта
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: 'John' };
getProperty(user, 'name'); // OK
getProperty(user, 'xyz'); // ОШИБКА! нет такого свойства

// Constraint на значение параметра
function extractKey<T extends Record<string, any>, 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 extracted = extractKey({ id: 1, name: 'John', age: 30 }, ['id', 'name']);
// extracted имеет тип: { id: number; name: string; }

7. Практический пример: Typed API Client

// Реальный пример из production кода

interface ApiClient {
  get<T>(url: string): Promise<T>;
  post<T, D>(url: string, data: D): Promise<T>;
  put<T, D>(url: string, data: D): Promise<T>;
  delete<T>(url: string): Promise<T>;
}

class HttpClient implements ApiClient {
  async get<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json() as Promise<T>;
  }

  async post<T, D>(url: string, data: D): Promise<T> {
    const response = await fetch(url, {
      method: 'POST',
      body: JSON.stringify(data),
      headers: { 'Content-Type': 'application/json' }
    });
    return response.json() as Promise<T>;
  }

  async put<T, D>(url: string, data: D): Promise<T> {
    const response = await fetch(url, {
      method: 'PUT',
      body: JSON.stringify(data),
      headers: { 'Content-Type': 'application/json' }
    });
    return response.json() as Promise<T>;
  }

  async delete<T>(url: string): Promise<T> {
    const response = await fetch(url, { method: 'DELETE' });
    return response.json() as Promise<T>;
  }
}

// Использование
const client = new HttpClient();

interface User {
  id: number;
  name: string;
  email: string;
}

const user = await client.get<User>('/api/users/1');
console.log(user.name); // OK
console.log(user.xyz); // ОШИБКА!

const newUser = await client.post<User, Omit<User, 'id'>>('/api/users', {
  name: 'Jane',
  email: 'jane@example.com'
});

8. Когда НЕ использовать Generics

// ❌ Переусложнение
function process<T, U, V, W extends T, X extends U>(a: W, b: X): V {
  // Too much magic!
}

// ✅ Просто и понятно
function process(user: User, settings: Settings): Result {
  // Clear and maintainable
}

// KISS принцип: используй generics когда они действительно нужны

Итого

Generics — это must-have навык для modern TypeScript разработчика:

  • Type Safety: IDE и компилятор помогают поймать ошибки
  • Reusability: один код работает с разными типами
  • Documentation: типы сами документируют код
  • Performance: никакого runtime overhead

Мой опыт:

  • Использую генерики ежедневно в production коде
  • Сложные conditional types и constraints — second nature
  • Понимаю как работает TypeScript inference
  • Могу быстро разобраться в сложных generic типах