Event Bus и паттерн Observer в TypeScript: от теории к практике
Современная веб-разработка требует гибкой и масштабируемой архитектуры. Два ключевых паттерна, которые помогают организовать взаимодействие между компонентами — это Event Bus и Observer. В этой статье мы разберем их реализацию на TypeScript, преимущества и практические сценарии использования.
Что такое паттерн Observer?
Паттерн Observer (Наблюдатель) — это поведенческий шаблон проектирования, который определяет зависимость "один ко многим" между объектами. Когда один объект (издатель) изменяет свое состояние, все зависимые объекты (подписчики) автоматически уведомляются и обновляются.
Основные элементы паттерна:
- Subject (Издатель) — объект, который отслеживает изменения и уведомляет подписчиков.
- Observer (Наблюдатель) — объект, который подписывается на изменения издателя.
- Event Bus (Шина событий) — централизованный механизм, который управляет подписками и отправкой событий между компонентами.
Реализация Event Bus на TypeScript
TypeScript предоставляет отличные возможности для строгой типизации, что делает реализацию Event Bus безопасной и предсказуемой. Рассмотрим базовую реализацию:
type EventHandler<T = any> = (payload: T) => void;
class EventBus {
private listeners: Map<string, EventHandler[]> = new Map();
// Подписка на событие
subscribe<T>(event: string, handler: EventHandler<T>): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(handler);
}
// Отписка от события
unsubscribe<T>(event: string, handler: EventHandler<T>): void {
const handlers = this.listeners.get(event);
if (handlers) {
this.listeners.set(
event,
handlers.filter(h => h !== handler)
);
}
}
// Публикация события
emit<T>(event: string, payload: T): void {
const handlers = this.listeners.get(event);
if (handlers) {
handlers.forEach(handler => handler(payload));
}
}
// Очистка всех подписок
clear(): void {
this.listeners.clear();
}
}
export const eventBus = new EventBus();
Пример использования с типизированными событиями
Для повышения типобезопасности можно определить интерфейсы событий:
// Определяем типы событий
interface AppEvents {
'user:login': { userId: string; timestamp: number };
'user:logout': { userId: string };
'data:update': { id: string; data: unknown };
'notification:show': { message: string; type: 'success' | 'error' | 'info' };
}
// Создаем типизированную шину
class TypedEventBus {
private listeners: Map<string, Function[]> = new Map();
on<K extends keyof AppEvents>(
event: K,
handler: (payload: AppEvents[K]) => void
): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(handler);
}
emit<K extends keyof AppEvents>(
event: K,
payload: AppEvents[K]
): void {
const handlers = this.listeners.get(event);
if (handlers) {
handlers.forEach(handler => handler(payload));
}
}
off<K extends keyof AppEvents>(
event: K,
handler: (payload: AppEvents[K]) => void
): void {
const handlers = this.listeners.get(event);
if (handlers) {
this.listeners.set(
event,
handlers.filter(h => h !== handler)
);
}
}
}
export const typedBus = new TypedEventBus();
Практический пример: система уведомлений
Рассмотрим реальный сценарий — создание системы уведомлений с использованием Event Bus:
// Компонент уведомлений
class NotificationService {
constructor() {
typedBus.on('notification:show', this.handleNotification);
typedBus.on('user:login', this.handleUserLogin);
}
private handleNotification = (payload: { message: string; type: string }) => {
console.log(`[${payload.type.toUpperCase()}] ${payload.message}`);
// Здесь можно добавить отображение toast-уведомления
};
private handleUserLogin = (payload: { userId: string }) => {
console.log(`Пользователь ${payload.userId} вошел в систему`);
typedBus.emit('notification:show', {
message: 'Добро пожаловать!',
type: 'success'
});
};
public destroy(): void {
typedBus.off('notification:show', this.handleNotification);
typedBus.off('user:login', this.handleUserLogin);
}
}
// Компонент аутентификации
class AuthService {
public login(userId: string): void {
//