Что такое Generics в TypeScript и как их использовать в Node.js проектах?

Ответ

Generics (обобщенные типы) в TypeScript — это механизм для создания компонентов, которые могут работать с различными типами данных, сохраняя при этом строгую типизацию. В Node.js разработке это особенно полезно для создания переиспользуемых сервисов, репозиториев и утилит.

Базовый пример с функцией:

// Обобщенная функция для логирования любого типа данных
function logData<T>(data: T): void {
  console.log(`[${new Date().toISOString()}]`, data);
}

logData<string>('User logged in');
logData<number>(404);
logData<{ id: number; name: string }>({ id: 1, name: 'John' });

Практическое применение в Node.js:

  1. Обобщенные репозитории для работы с БД:
    
    interface Repository<T> {
    findById(id: string): Promise<T | null>;
    save(entity: T): Promise<T>;
    delete(id: string): Promise<boolean>;
    }

class BaseRepository implements Repository { constructor(private model: any) {}

async findById(id: string): Promise<T | null> { return this.model.findById(id); }

async save(entity: T): Promise { return this.model.create(entity); } }

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

const userRepository = new BaseRepository(UserModel);


2. **Обобщенные сервисы:**
```typescript
class CacheService<T> {
  private cache = new Map<string, T>();

  set(key: string, value: T): void {
    this.cache.set(key, value);
  }

  get(key: string): T | undefined {
    return this.cache.get(key);
  }

  // Использование с ограничениями (constraints)
  getAllByType<U extends T>(typeGuard: (item: T) => item is U): U[] {
    return Array.from(this.cache.values()).filter(typeGuard);
  }
}

// Использование
const userCache = new CacheService<{ id: string; name: string }>();
userCache.set('user1', { id: '1', name: 'Alice' });
  1. Обобщенные middleware в Express:
    
    function validateBody<T>(schema: Joi.Schema<T>): RequestHandler {
    return (req, res, next) => {
    const { error, value } = schema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.message });
    }
    req.validatedBody = value as T;
    next();
    };
    }

// Использование interface LoginDto { email: string; password: string; }

app.post('/login', validateBody(loginSchema), loginController);



**Преимущества в Node.js контексте:**
- Повторное использование кода для разных сущностей
- Автодополнение и проверка типов в IDE
- Безопасность типов при работе с внешними API и базами данных
- Улучшенная читаемость и поддерживаемость кода

Ответ 18+ 🔞

Слушай, ну это ж просто пиздец как круто, когда можно один раз написать код, а потом подсовывать ему любые типы, и он не охуевает! Generics в TypeScript — это как универсальная отвертка, которая подходит и под крестик, и под плоский, и под звёздочку. В Node.js без них — просто караул, блядь, особенно когда пишешь сервисы или работаешь с базой.

Смотри, самый простой пример, чтобы въехать:

// Функция, которая может принять что угодно и залогировать
function logData<T>(data: T): void {
  console.log(`[${new Date().toISOString()}]`, data);
}

// И ей похуй, что ты туда суёшь — строку, число или объект
logData<string>('Юзер зашёл');
logData<number>(404);
logData<{ id: number; name: string }>({ id: 1, name: 'Васян' });

Вот видишь? Одна функция, а работает со всем. Красота, ёпта!

А теперь где это реально вкатывает в Node.js:

  1. Репозитории для базы данных — просто песня! Раньше для каждой сущности (User, Product, Order) надо было писать свой репозиторий, копипастить одни и те же методы. А теперь? Овердохуища удобства!
// Объявляем интерфейс, что должен уметь любой репозиторий
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  save(entity: T): Promise<T>;
  delete(id: string): Promise<boolean>;
}

// Пишем ОДИН базовый класс на все случаи жизни
class BaseRepository<T> implements Repository<T> {
  constructor(private model: any) {}

  async findById(id: string): Promise<T | null> {
    return this.model.findById(id);
  }

  async save(entity: T): Promise<T> {
    return this.model.create(entity);
  }
}

// А теперь используем. Создаём тип для юзера...
interface IUser {
  id: string;
  email: string;
  name: string;
}

// ...и на раз-два делаем репозиторий именно для него!
const userRepository = new BaseRepository<IUser>(UserModel);
// Всё! findById и save уже будут знать, что работают с IUser. Автодополнение в IDE ликует, доверия ебать ноль к ошибкам.
  1. Сервис кэширования — тоже хитрая жопа получается. Хочешь кэшировать строки? Пожалуйста. Хочешь целые объекты? Да похуй, главное тип указать.
class CacheService<T> {
  private cache = new Map<string, T>();

  set(key: string, value: T): void {
    this.cache.set(key, value);
  }

  get(key: string): T | undefined {
    return this.cache.get(key);
  }

  // А вот это вообще магия: можно фильтровать закэшированное по типу!
  getAllByType<U extends T>(typeGuard: (item: T) => item is U): U[] {
    return Array.from(this.cache.values()).filter(typeGuard);
  }
}

// Используем для пользователей
const userCache = new CacheService<{ id: string; name: string }>();
userCache.set('user1', { id: '1', name: 'Алиса' });
// Теперь userCache.get() будет возвращать именно объект с id и name, а не какую-то хуйню.
  1. Middleware в Express — тут вообще без generics жить невозможно. Валидация тела запроса — типичная задача. Раньше после валидации в req.body был any, и можно было накосячить. А теперь...
function validateBody<T>(schema: Joi.Schema<T>): RequestHandler {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.message });
    }
    // Вот тут магия! Говорим TypeScript: "поверь, это тип T".
    req.validatedBody = value as T;
    next();
  };
}

// Объявляем, что ждём на вход в /login
interface LoginDto {
  email: string;
  password: string;
}

// И вуаля! В контроллере в req.validatedBody будет строго LoginDto
app.post('/login', validateBody<LoginDto>(loginSchema), loginController);

Итог, чувак: Писать без generics в TypeScript на Node.js — это как ехать на Ferrari, но на первой передаче. Преимущества — просто удивление пиздец:

  • Переиспользование кода: Один раз написал BaseRepository — и на все сущности хватит.
  • Безопасность и автодополнение: IDE не позволит тебе сохранить в базу поля, которых нет в типе. Спасёт от тупых ошибок в рантайме.
  • Читаемость: Сразу видно, с чем работает функция или класс. Не надо гадать, что там за data: any прилетело.

Короче, generics — это не просто фича языка, это must have для любого адекватного бэкенда на TypeScript. Осваивай и не бзди!

Видео-ответы