Иерархии контроллеров

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


Родительские и дочерние контроллеры

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

  • Родительский контроллер — контроллер, который содержит в DOM элементы с другими контроллерами.
  • Дочерний контроллер — контроллер, вложенный в DOM-дерево родителя, с которым он может взаимодействовать.

Пример структуры

<div data-controller="parent">
  <div data-controller="child" data-child-target="item"></div>
  <div data-controller="child" data-child-target="item"></div>
</div>

В этом примере parent является родителем для двух дочерних контроллеров child.


Связи через Targets и Values

Stimulus предлагает targets и values как основной механизм управления вложенными контроллерами:

  • Targets — позволяют ссылаться на определённые элементы внутри контроллера.
  • Values — позволяют хранить состояние элемента и синхронизировать его с DOM.

Пример использования Targets для взаимодействия родителя и дочернего контроллера:

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

export default class extends Controller {
  static targets = ["item"];

  connect() {
    console.log(`Найдено ${this.itemTargets.length} дочерних элемента`);
  }

  highlightItems() {
    this.itemTargets.forEach(item => item.classList.add("highlight"));
  }
}
// child_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["item"];

  connect() {
    console.log("Child подключен");
  }
}

Доступ к родительскому контроллеру

Stimulus позволяет дочернему контроллеру получать доступ к родительскому через встроенные API:

  • this.application.getControllerForElementAndIdentifier(element, identifier) — возвращает экземпляр контроллера для указанного элемента и идентификатора.
  • Используется, если необходимо вызвать метод родителя или получить его состояние.

Пример:

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

export default class extends Controller {
  connect() {
    const parent = this.application.getControllerForElementAndIdentifier(
      this.element.closest("[data-controller='parent']"),
      "parent"
    );
    parent.highlightItems();
  }
}

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

Stimulus активно использует Custom Events для общения между контроллерами:

  • Родительский контроллер может слушать события, испускаемые дочерним.
  • Дочерний контроллер может триггерить события при изменении состояния.

Пример:

// child_controller.js
this.element.dispatchEvent(new CustomEvent("child:clicked", {
  bubbles: true,
  detail: { value: this.element.textContent }
}));

// parent_controller.js
this.element.addEventListener("child:clicked", event => {
  console.log(`Событие от дочернего: ${event.detail.value}`);
});

Использование bubbles: true позволяет событию подниматься по DOM до родителя, что упрощает взаимодействие в иерархии.


Автоматическая интеграция с nested controllers

При динамическом добавлении элементов в DOM, Stimulus автоматически инициализирует вложенные контроллеры, если у них присутствует атрибут data-controller. Это делает иерархию контроллеров гибкой и масштабируемой:

const newChild = document.createElement("div");
newChild.setAttribute("data-controller", "child");
parentElement.appendChild(newChild);
// Stimulus автоматически подключит child_controller к newChild

Важные рекомендации

  • Минимизировать прямые зависимости между контроллерами. Предпочтительнее использовать события, чтобы не создавать тесные связи.
  • Использовать targets и values для обмена состоянием, особенно если контроллеры управляют визуальными элементами.
  • Иерархия не обязательно отражает DOM строго — контроллер может взаимодействовать с любым элементом, доступным через селекторы или методы API.
  • Custom Events позволяют строить чистую архитектуру с минимальным дублированием кода.

Практическая схема иерархии

  1. Parent Controller — управляет коллекцией элементов, имеет Targets и методы для массовых операций.
  2. Child Controller — отвечает за конкретный элемент, эмитирует события при изменении состояния.
  3. Shared State via Values — хранит конфигурацию или состояние для родителя и дочерних контроллеров.
  4. Communication via Events — дочерние контроллеры оповещают родителя о действиях, родитель обновляет Targets или Values.

Эта схема позволяет создавать сложные, интерактивные интерфейсы с минимальными связями между компонентами и полной управляемостью через Stimulus API.