Event Bus и паттерн Observer в TypeScript: реализация и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 {
    //                    

Рекомендаций