Медиатор для координации компонентов

Stimulus — это легковесный фреймворк, который организует поведение веб-страниц через контроллеры, связывая их с DOM с помощью атрибутов data-controller, data-action и data-target. В сложных приложениях часто возникает необходимость координации нескольких контроллеров, обмена данными и событий между ними. Здесь на помощь приходит паттерн Медиатор, обеспечивающий централизованное управление взаимодействием компонентов без их тесной связки друг с другом.

Концепция медиатора

Медиатор выступает как посредник, который принимает события от одного контроллера и уведомляет другие контроллеры о необходимости действия. Основные преимущества:

  • Слабая связанность: контроллеры не знают о существовании друг друга.
  • Централизованное управление: все события проходят через один объект.
  • Упрощение тестирования: проще изолировать поведение каждого контроллера.

В контексте Stimulus медиатор обычно реализуется как отдельный JavaScript-модуль, который экспортирует методы подписки, публикации событий и управления состоянием.

Реализация медиатора

Простейшая структура медиатора может выглядеть так:

export const mediator = {
  channels: {},

  subscribe(channel, callback) {
    if (!this.channels[channel]) {
      this.channels[channel] = [];
    }
    this.channels[channel].push(callback);
  },

  publish(channel, data) {
    if (!this.channels[channel]) return;
    this.channels[channel].forEach(callback => callback(data));
  },

  unsubscribe(channel, callback) {
    if (!this.channels[channel]) return;
    this.channels[channel] = this.channels[channel].filter(cb => cb !== callback);
  }
};

Ключевые моменты:

  • channels хранит все подписки.
  • Метод subscribe регистрирует обработчики для определённого канала.
  • Метод publish рассылает данные всем подписчикам.
  • Метод unsubscribe позволяет удалять обработчики, предотвращая утечки памяти.

Подключение медиатора в контроллер

Контроллер Stimulus может использовать медиатор для уведомления других компонентов или реагирования на внешние события:

import { Controller } from "@hotwired/stimulus";
import { mediator } from "../mediator";

export default class extends Controller {
  connect() {
    mediator.subscribe('user:updated', this.updateUser.bind(this));
  }

  disconnect() {
    mediator.unsubscribe('user:updated', this.updateUser.bind(this));
  }

  updateUser(data) {
    this.element.textContent = `Имя пользователя: ${data.name}`;
  }

  notifyChange() {
    const newName = this.element.querySelector('input').value;
    mediator.publish('user:updated', { name: newName });
  }
}

Особенности подключения:

  • connect и disconnect обеспечивают корректное управление подписками.
  • Методы контроллера могут публиковать события через медиатор без прямого взаимодействия с другими контроллерами.
  • Такой подход предотвращает жёсткую связанность, позволяя менять внутреннюю логику контроллера, не затрагивая других.

Использование нескольких каналов

Медиатор поддерживает работу с различными типами событий через каналы:

mediator.subscribe('cart:itemAdded', (item) => {
  console.log('Товар добавлен в корзину:', item);
});

mediator.subscribe('cart:itemRemoved', (itemId) => {
  console.log('Товар удален из корзины, ID:', itemId);
});

Контроллеры могут публиковать события на соответствующих каналах:

mediator.publish('cart:itemAdded', { id: 1, name: 'Ноутбук' });
mediator.publish('cart:itemRemoved', 1);

Такое раздельное использование каналов позволяет структурировать события по контексту и избегать конфликтов между контроллерами.

Расширение медиатора

Для более сложных приложений медиатор может быть расширен:

  • Поддержка приоритетов: обработчики могут иметь приоритет выполнения.
  • Фильтрация событий: подписчик может принимать только данные, удовлетворяющие определённым условиям.
  • Логирование и отладка: централизованный журнал всех публикаций облегчает мониторинг.

Пример фильтрующего обработчика:

mediator.subscribe('user:updated', (data) => {
  if (data.role === 'admin') {
    console.log('Обновлён администратор:', data.name);
  }
});

Интеграция с жизненным циклом Stimulus

Stimulus предоставляет события жизненного цикла connect, disconnect и initialize, которые идеально подходят для управления подписками на медиатор:

  • initialize: подготовка внутреннего состояния контроллера.
  • connect: регистрация подписок на медиатор.
  • disconnect: отписка от всех каналов для предотвращения утечек памяти.

Следование этому паттерну обеспечивает устойчивую архитектуру, где контроллеры остаются независимыми и легко поддерживаемыми, даже если количество компонентов растёт.

Применение медиатора в реальных сценариях

  • Формы с зависимыми полями: один контроллер обновляет состояние, другой реагирует на изменения.
  • Обновление корзины: несколько компонентов на странице реагируют на добавление/удаление товаров.
  • Панели уведомлений: медиатор управляет централизованным выводом сообщений для разных модулей.

Использование медиатора в Stimulus позволяет строить сложные интерфейсы без усиления взаимной зависимости контроллеров, сохраняя лёгкость и читабельность кода.