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

Ответ

Да, активно использую дженерики (Generics) в TypeScript для Node.js-проектов. Они необходимы для создания переиспользуемых, типобезопасных компонентов, когда точный тип известен только в момент использования.

Практические примеры из моего опыта:

  1. Универсальные функции-хелперы:

    // Функция для оборачивания ответа API в стандартный формат
    interface ApiResponse<T> {
      success: boolean;
      data: T;
      error?: string;
    }
    
    function createResponse<T>(data: T, error?: string): ApiResponse<T> {
      return {
        success: !error,
        data,
        ...(error && { error })
      };
    }
    
    // Использование с разными типами данных
    const userResponse = createResponse<User>({ id: 1, name: 'John' });
    const listResponse = createResponse<string[]>(['item1', 'item2']);
  2. Типизация репозиториев для работы с БД (паттерн Repository):

    interface IRepository<T> {
      findById(id: number): Promise<T | null>;
      create(entity: Partial<T>): Promise<T>;
      update(id: number, updates: Partial<T>): Promise<T | null>;
    }
    
    class UserRepository implements IRepository<User> {
      async findById(id: number): Promise<User | null> {
        // ... логика поиска пользователя в БД
      }
      // ... реализация других методов
    }
  3. Работа с промисами и асинхронными операциями:

    async function fetchData<T>(url: string): Promise<T> {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Ошибка сети');
      return response.json() as Promise<T>; // Указываем ожидаемый тип данных
    }
    
    // Вызов с конкретным типом
    interface Product { id: number; title: string; }
    const product = await fetchData<Product>('/api/products/1');

Дженерики делают код более предсказуемым и защищают от ошибок типов на этапе компиляции.

Ответ 18+ 🔞

Ну, слушай, я тут подумал — дженерики в TypeScript, это ж просто ёперный театр! Как будто у тебя есть один универсальный ключ, который подходит ко всем замкам, только ты заранее знаешь, к какому именно замку он сейчас полезет. Без них — пиздопроебина полная, начинаешь везде any пихать, а потом волнение ебать, когда в рантайме всё разваливается.

Вот смотри, реальные примеры, где без них нихуя:

  1. Хелперы, которые не хотят быть идиотами. Ну представь, тебе надо обернуть ответ от API в одну обёртку. Без дженериков ты либо плодишь кучу почти одинаковых интерфейсов, либо ставишь data: any и молишься. А с ними — красота.

    // Объявляем формат ответа: успех, данные (любого типа Т) и опциональная ошибка
    interface ApiResponse<T> {
      success: boolean;
      data: T;
      error?: string;
    }
    
    // Функция, которая это создаёт. Тип Т она узнает, когда её вызовут.
    function createResponse<T>(data: T, error?: string): ApiResponse<T> {
      return {
        success: !error,
        data,
        ...(error && { error })
      };
    }
    
    // И вот магия: вызываем с разным хламом, и всё типобезопасно!
    const userResponse = createResponse<User>({ id: 1, name: 'Вася' }); // ApiResponse<User>
    const listResponse = createResponse<string[]>(['хлеб', 'молоко']); // ApiResponse<string[]>

    Раньше data был бы any, и можно было бы туда запихнуть что угодно, а теперь — ни хуя себе, контроль.

  2. Репозитории для базы данных. Тут вообще без вариантов. Паттерн Repository — это когда у тебя для каждой сущности (User, Product) методы одни и те же (find, create, update), но работают с разными типами. Дженерики спасают от писанины.

    // Общий контракт для всех репозиториев. T — это та сущность, с которой он работает.
    interface IRepository<T> {
      findById(id: number): Promise<T | null>;
      create(entity: Partial<T>): Promise<T>;
      update(id: number, updates: Partial<T>): Promise<T | null>;
    }
    
    // Реализуем для пользователя. Говорим: "Берём общий шаблон IRepository и вместо T везде подставляем User".
    class UserRepository implements IRepository<User> {
      async findById(id: number): Promise<User | null> {
        // ... идём в базу, ищем юзера
      }
      // ... остальные методы
    }

    Теперь если в методе update для UserRepository я попробую передать поле price, TypeScript сразу скажет: "Чувак, ты охуел? У User нет поля price". И это офигенно.

  3. Асинхронщина и промисы. Частая задача: написать функцию-обёртку для fetch. Без дженериков она возвращала бы Promise<any>, и опять ты в темноте.

    async function fetchData<T>(url: string): Promise<T> {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Ну всё, опять ошибка сети, ебать колотить');
      return response.json() as Promise<T>; // Говорим компилятору: "Поверь мне, там будет тип T"
    }
    
    // Используем. Говорим функции, что ждём с сервера именно продукт.
    interface Product { id: number; title: string; }
    const product = await fetchData<Product>('/api/products/1'); // product теперь имеет тип Product

    Если сервер вдруг пришлёт тебе массив, а ты ждёшь один объект — TypeScript тебя предупредит, когда ты начнёшь обращаться к product.title, а там undefined. Удивление пиздец, но приятное, потому что на этапе компиляции, а не в продакшене.

Короче, дженерики — это не какая-то академическая хуйня. Это реальный инструмент, который делает код крепче, как хороший самогон. Терпения ноль ебать на отладку глупых ошибок с типами, когда можно заставить компилятор делать эту работу за тебя.