Дженерик-классы
Классы могут принимать параметр типа. Полезно для коллекций, стеков, хранилищ.
Базовый пример
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);
numbers.push('a'); // ошибка
С ограничением
class Repository<T extends { id: number }> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
}
find(id: number): T | undefined {
return this.items.find((item) => item.id === id);
}
}
const users = new Repository<{ id: number; name: string }>();
users.add({ id: 1, name: 'Алиса' });
Несколько параметров
class Pair<K, V> {
constructor(public key: K, public value: V) {}
swap(): Pair<V, K> {
return new Pair(this.value, this.key);
}
}
const p = new Pair('age', 25);
const swapped = p.swap(); // Pair<number, string>
Методы с дженериками
class Collection<T> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
}
map<U>(fn: (item: T) => U): U[] {
return this.items.map(fn);
}
}
const col = new Collection<number>();
col.add(1);
col.add(2);
const strings = col.map((n) => n.toString()); // string[]
Метод может добавить свой параметр U, независимый от T класса.
Наследование
class Base<T> {
constructor(public value: T) {}
}
class Wrapper<T> extends Base<T> {
wrap(): T[] {
return [this.value];
}
}
const w = new Wrapper('hello');
w.value; // string
Наследование с фиксированным типом
class StringWrapper extends Base<string> {
toUpperCase(): string {
return this.value.toUpperCase();
}
}
Абстрактный класс
abstract class Storage<T> {
abstract get(key: string): T | undefined;
abstract set(key: string, value: T): void;
}
class MemoryStorage<T> extends Storage<T> {
private data = new Map<string, T>();
get(key: string): T | undefined {
return this.data.get(key);
}
set(key: string, value: T): void {
this.data.set(key, value);
}
}
Статические методы
Статические методы не могут использовать T класса:
class Container<T> {
// static create(): Container<T> {} // ошибка
static create<U>(value: U): Container<U> {
return new Container<U>(value);
}
constructor(public value: T) {}
}
const c = Container.create('hello'); // Container<string>
Практика: EventEmitter
type Events = Record<string, unknown>;
class EventEmitter<T extends Events> {
private listeners: { [K in keyof T]?: ((payload: T[K]) => void)[] } = {};
on<K extends keyof T>(event: K, listener: (payload: T[K]) => void): void {
this.listeners[event] = this.listeners[event] ?? [];
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, payload: T[K]): void {
this.listeners[event]?.forEach((l) => l(payload));
}
}
type AppEvents = {
login: { userId: number };
logout: { userId: number };
};
const bus = new EventEmitter<AppEvents>();
bus.on('login', ({ userId }) => console.log(userId));
bus.emit('login', { userId: 1 });
Итоги
- Классы принимают параметр типа
- Методы могут добавлять свои параметры
- Наследование сохраняет дженерик
- Абстрактные классы работают
- Статические методы — свои параметры