Generic интерфейсы и классы
Generic интерфейсы и классы — конструкции TypeScript, параметризованные типовыми переменными, которые позволяют описывать универсальные контракты и реализации, сохраняя строгую типизацию при разных типах данных.
Зачем нужно
Generic-параметры на уровне интерфейса или класса позволяют описать единый контракт (например, репозиторий, коллекцию, обёртку) и переиспользовать его с разными типами данных без копирования кода. Это основа для паттернов Repository, Service, Builder, Observable и других.
Где используется
- Репозитории и сервисы:
Repository<User>,Service<Order> - Коллекции и контейнеры:
Stack<T>,Queue<T>,Cache<K, V> - Обёртки результатов:
ApiResponse<T>,Paginated<T> - Паттерн Builder, Factory
- React-компоненты с generic props
Основной контент
Generic интерфейс
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll: Promise<T>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
interface User {
id: string;
name: string;
email: string;
}
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// реализация
return null;
}
async findAll: Promise<User> { return ; }
async save(user: User): Promise<User> { return user; }
async delete(id: string): Promise<void> {}
}
Generic интерфейс с несколькими параметрами
interface Cache<K, V> {
get(key: K): V | undefined;
set(key: K, value: V): void;
has(key: K): boolean;
delete(key: K): boolean;
}
class MapCache<K, V> implements Cache<K, V> {
private store = new Map<K, V>;
get(key: K) { return this.store.get(key); }
set(key: K, value: V) { this.store.set(key, value); }
has(key: K) { return this.store.has(key); }
delete(key: K) { return this.store.delete(key); }
}
const cache = new MapCache<string, number>;
cache.set("count", 42);
const val = cache.get("count"); // number | undefined
Generic класс
class Stack<T> {
private items: T = ;
push(item: T): void {
this.items.push(item);
}
pop: T | undefined {
return this.items.pop();
}
peek: T | undefined {
return this.items[this.items.length - 1];
}
get size: number {
return this.items.length;
}
}
const numbers = new Stack<number>;
numbers.push(1);
numbers.push(2);
const top = numbers.pop(); // number | undefined
const words = new Stack<string>;
words.push("hello");
Ограничения в generic классах
interface Identifiable {
id: string;
}
class EntityService<T extends Identifiable> {
private entities = new Map<string, T>;
add(entity: T): void {
this.entities.set(entity.id, entity); // OK — T имеет id
}
findById(id: string): T | undefined {
return this.entities.get(id);
}
}
const userService = new EntityService<User>;
userService.add({ id: "1", name: "Alice", email: "a@b.com" });
Generic интерфейс с callable/index signature
interface Transformer<TIn, TOut> {
(input: TIn): TOut;
}
interface Collection<T> {
[index: number]: T;
length: number;
}
Частые ошибки
- Не указывать тип при создании экземпляра —
new Stackбез параметра дастStack<unknown>, лучше явноnew Stack<number>. - Смешивать generic-параметр класса и метода — параметр класса
Tдоступен во всех методах; не переиспользуйте то же имя в параметре метода. - Реализовывать generic интерфейс с конкретным типом не полностью — все методы должны быть реализованы с правильными сигнатурами.
- Использовать generic там, где достаточно
unknown— если тип не важен и не используется в нескольких местах — generic избыточен.
Связанные темы
- Generic функции
- Ограничения дженериков -- extends
- Дженерики -- практические примеры
- interface -- объявление и использование
- implements -- реализация интерфейсов
- _MOC TypeScript