Шаблоны взаимодействия контроллеров

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

События как средство связи

Контроллеры Stimulus используют стандартную модель событий DOM для взаимодействия. Внутри одного контроллера события обрабатываются через методы, объявленные с помощью data-action. Например, кнопка может инициировать событие click, которое вызывает метод контроллера:

<button data-action="click->menu#toggle">Меню</button>
export default class extends Controller {
  toggle() {
    this.element.classList.toggle("open");
    const event = new CustomEvent("menu:toggled", { bubbles: true });
    this.element.dispatchEvent(event);
  }
}

Событие menu:toggled можно поймать в другом контроллере, назначив слушатель на общий родительский элемент:

export default class extends Controller {
  connect() {
    this.element.addEventListener("menu:toggled", this.menuToggled.bind(this));
  }

  menuToggled(event) {
    console.log("Меню было открыто или закрыто");
  }
}

Ключевой момент: использование CustomEvent с bubbles: true обеспечивает проброс события вверх по DOM, что делает возможным коммуникацию между разными контроллерами без жёсткой зависимости.

Значения (Values) для обмена данными

Stimulus предоставляет механизм значений для управления состоянием и передачи данных между контроллерами через атрибуты DOM. Значения определяются в контроллере через static values:

export default class extends Controller {
  static values = { count: Number }

  increment() {
    this.countValue++
    this.dispatch("changed", { detail: { count: this.countValue } })
  }
}

В другом контроллере можно подписаться на событие changed:

export default class extends Controller {
  connect() {
    this.element.addEventListener("counter:changed", this.updateCounter.bind(this))
  }

  updateCounter(event) {
    const newCount = event.detail.count
    console.log("Новое значение счётчика:", newCount)
  }
}

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

Действия (Actions) для вызова методов

Механизм действий позволяет контроллерам реагировать на события DOM, такие как click, input, submit. Атрибут data-action связывает событие с методом контроллера. Он поддерживает множественные обработчики и даже передачу аргументов:

<input type="text" data-action="input->search#update value->search#filter" />

Контроллер search может обрабатывать несколько событий одновременно, управляя поведением одного элемента:

export default class extends Controller {
  update(event) {
    this.query = event.target.value
  }

  filter() {
    console.log("Фильтруем по:", this.query)
  }
}

Использование Target для взаимодействия между контроллерами

Targets позволяют контроллеру оперировать конкретными элементами внутри своего DOM-контейнера. При необходимости передачи данных между контроллерами, один контроллер может воздействовать на элементы другого через события или dispatch:

<div data-controller="modal">
  <div data-modal-target="content"></div>
</div>
<div data-controller="trigger">
  <button data-action="click->trigger#openModal">Открыть модальное окно</button>
</div>
// trigger_controller.js
export default class extends Controller {
  openModal() {
    this.element.dispatchEvent(new CustomEvent("modal:open", { bubbles: true }))
  }
}

// modal_controller.js
export default class extends Controller {
  static targets = ["content"]

  connect() {
    this.element.addEventListener("modal:open", () => {
      this.contentTarget.textContent = "Модальное окно открыто"
      this.element.classList.add("active")
    })
  }
}

Декларативное связывание через data-* атрибуты

Stimulus позволяет строить взаимодействие между контроллерами полностью декларативно. Все связи происходят через HTML-атрибуты:

  • data-controller — назначение контроллера элементу.
  • data-action — обработка событий DOM.
  • data-target — выделение элементов внутри контроллера.
  • data-*-value — определение и управление значениями.

Такой подход снижает связность компонентов и делает код более модульным и читаемым.

Совмещение нескольких контроллеров на одном элементе

Элемент может содержать несколько контроллеров, что позволяет разрабатывать сложные интерфейсы с независимыми слоями поведения:

<div data-controller="tabs dropdown" data-tabs-value="1" data-dropdown-value="false">
  <button data-action="click->tabs#select click->dropdown#toggle">Кнопка</button>
</div>

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

Практическая рекомендация

  • Избегать жёстких связей между контроллерами; использовать события и значения.
  • Определять targets и values для локального состояния и управляемых элементов.
  • Использовать кастомные события с bubbles: true для передачи данных между разными уровнями DOM.
  • Сохранять контроллеры небольшими и специализированными, что упрощает тестирование и поддержку кода.

Stimulus предлагает минималистичный, но мощный подход к организации взаимодействия между контроллерами, делая код декларативным, модульным и легко расширяемым.