Stimulus — это фреймворк JavaScript, ориентированный на минимальное вмешательство в существующую разметку HTML, что позволяет управлять поведением страниц без необходимости полного SPA-подхода. Одной из важных возможностей современного фронтенда является использование Custom Elements (пользовательских элементов), стандарта Web Components. Интеграция Stimulus с Custom Elements позволяет создавать высокоинкапсулированные, повторно используемые компоненты, сохраняя декларативный стиль управления поведением через контроллеры.
Custom Elements позволяют определить собственные HTML-теги с уникальной логикой. Для этого используется стандартная API:
class MyElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `<p>Привет из Custom Element!</p>`;
}
}
customElements.define('my-element', MyElement);
Ключевые моменты:
connectedCallback() вызывается при добавлении элемента
в DOM.attachShadow({ mode: 'open' }) создаёт Shadow DOM для
инкапсуляции стилей и структуры.customElements.define() регистрирует тег, который можно
использовать в HTML.Stimulus не требует специальных настроек для работы с Custom
Elements. Контроллер можно привязать через атрибут
data-controller прямо на пользовательском элементе:
<my-element data-controller="example"></my-element>
Контроллер Stimulus будет работать так же, как и на обычных HTML-элементах, обрабатывая события и взаимодействуя с DOM внутри элемента. Однако следует учитывать два нюанса:
Shadow DOM и доступ к элементам Stimulus по
умолчанию работает с обычным DOM. Если Custom Element использует Shadow
DOM, контроллеру необходимо получать доступ к
shadowRoot:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
connect() {
const shadow = this.element.shadowRoot;
const button = shadow.querySelector('button');
button.addEventListener('click', () => {
console.log('Кнопка нажата внутри Custom Element');
});
}
}Инициализация контроллеров после рендеринга Custom
Element Если элемент создаётся динамически через JavaScript,
контроллеры Stimulus могут не инициализироваться автоматически. В таких
случаях используется Application.start() или
application.load() после добавления элемента в
DOM.
Targets и Actions работают стандартно, но с учётом Shadow DOM потребуется обращаться к внутренним элементам через shadowRoot. Пример:
// HTML
<my-element data-controller="counter">
<button data-counter-target="increment">+</button>
<span data-counter-target="value">0</span>
</my-element>
// Controller
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["value", "increment"];
connect() {
this.count = 0;
this.incrementTarget.addEventListener('click', () => this.increment());
}
increment() {
this.count++;
this.valueTarget.textContent = this.count;
}
}
Для Shadow DOM элементы data-*-target находятся внутри
shadowRoot, поэтому необходимо модифицировать
обращение:
this.valueTarget = this.element.shadowRoot.querySelector('[data-counter-target="value"]');
this.incrementTarget = this.element.shadowRoot.querySelector('[data-counter-target="increment"]');
Stimulus поддерживает value properties, что позволяет передавать параметры в контроллер через HTML-атрибуты. В комбинации с Custom Elements это особенно удобно для конфигурации компонента:
<my-element data-controller="greeting" data-greeting-name-value="Мир"></my-element>
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = { name: String };
connect() {
console.log(`Привет, ${this.nameValue}!`);
}
}
Custom Element можно сделать полностью настраиваемым, передавая различные значения через атрибуты, которые будут автоматически использоваться контроллером Stimulus.
Custom Elements могут испускать свои события
(CustomEvent). Stimulus легко подписывается на них с
помощью data-action:
// Внутри Custom Element
this.dispatchEvent(new CustomEvent('custom-click', { bubbles: true }));
// В HTML
<my-element data-controller="listener" data-action="custom-click->listener#handleClick"></my-element>
// Controller
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
handleClick(event) {
console.log('Событие custom-click получено', event);
}
}
Особенности:
bubbles: true необходим, чтобы событие достигло
контроллера Stimulus.data-action.Stimulus поддерживает динамическую инициализацию контроллеров, что важно для компонентов, создаваемых на лету. Например, при загрузке Custom Element через fetch:
fetch('/components/widget.html')
.then(r => r.text())
.then(html => {
const container = document.createElement('div');
container.innerHTML = html;
document.body.appendChild(container);
application.load(container); // Инициализация Stimulus контроллеров
});
Такой подход позволяет создавать динамические компоненты с полной интеграцией Stimulus без необходимости пересоздавать приложение.
data-action.application.load().Stimulus и Custom Elements образуют мощное сочетание, позволяя создавать инкапсулированные, реактивные компоненты, при этом сохраняя минимализм и простоту архитектуры страницы.