Создание переиспользуемых компонентов

Stimulus изначально проектировался как минималистичный фреймворк для добавления поведения к уже существующей HTML-разметке. В отличие от компонентных SPA-фреймворков, здесь компонент — это не виртуальное дерево и не шаблон, а связка HTML + контроллер + соглашения об именовании. Переиспользуемость достигается не за счёт абстрактных фабрик, а через композицию, параметры и чёткие границы ответственности.

Переиспользуемый компонент в Stimulus — это:

  • универсальная HTML-структура;
  • контроллер, не зависящий от конкретного контекста страницы;
  • конфигурация через data-* атрибуты;
  • отсутствие жёстких связей с глобальным состоянием.

Базовая структура компонента

Минимальный компонент состоит из HTML-узла и контроллера:

<div data-controller="counter">
  <span data-counter-target="value">0</span>
  <button data-action="counter#increment">+</button>
</div>
import { Controller } from "@hotwired/stimulus"

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

  increment() {
    this.valueTarget.textContent =
      parseInt(this.valueTarget.textContent, 10) + 1
  }
}

Для переиспользуемости важно, чтобы:

  • контроллер не предполагал конкретного DOM-контекста;
  • все зависимости описывались через targets, values и classes;
  • логика не выходила за рамки текущего элемента (this.element).

Параметризация компонентов через values

Stimulus предоставляет механизм values для передачи данных из HTML в контроллер. Это основной инструмент настройки компонентов без изменения кода.

<div
  data-controller="counter"
  data-counter-start-value="10"
  data-counter-step-value="5"
>
  <span data-counter-target="value"></span>
  <button data-action="counter#increment">+</button>
</div>
export default class extends Controller {
  static targets = ["value"]
  static values = {
    start: Number,
    step: Number
  }

  connect() {
    this.valueTarget.textContent = this.startValue
  }

  increment() {
    this.valueTarget.textContent =
      parseInt(this.valueTarget.textContent, 10) + this.stepValue
  }
}

Ключевые свойства values:

  • автоматическое приведение типов;
  • реактивность (изменение атрибута обновляет значение);
  • единый API для всех экземпляров компонента.

Компонент становится полностью настраиваемым декларативно, без изменения JavaScript.

Изоляция логики и отсутствие побочных эффектов

Переиспользуемый компонент не должен:

  • обращаться к document напрямую;
  • искать элементы вне this.element;
  • модифицировать глобальные объекты;
  • полагаться на порядок загрузки других контроллеров.

Плохая практика:

document.querySelector(".modal").classList.add("open")

Корректный подход:

this.element.classList.add("open")

Если требуется взаимодействие между компонентами, используется событийная модель.

Связь компонентов через DOM-события

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

this.element.dispatchEvent(
  new CustomEvent("counter:changed", {
    detail: { value: newValue },
    bubbles: true
  })
)
<div
  data-controller="logger"
  data-action="counter:changed->logger#log"
></div>
export default class extends Controller {
  log(event) {
    console.log(event.detail.value)
  }
}

Компоненты остаются независимыми и могут использоваться в любом сочетании.

Повторное использование через композицию

Stimulus не навязывает иерархию компонентов. Один DOM-элемент может иметь несколько контроллеров:

<div
  data-controller="dropdown analytics"
  data-action="click->dropdown#toggle"
>

Каждый контроллер отвечает за свою задачу:

  • dropdown — управление состоянием;
  • analytics — сбор событий.

Композиция позволяет:

  • переиспользовать мелкие контроллеры;
  • избегать монолитных классов;
  • подключать поведение по необходимости.

Использование classes для визуальной универсальности

Для работы с CSS-классами используется API classes, исключающее жёсткое кодирование имён классов.

<div
  data-controller="toggle"
  data-toggle-active-class="is-active"
>
export default class extends Controller {
  static classes = ["active"]

  toggle() {
    this.element.classList.toggle(this.activeClass)
  }
}

Преимущества:

  • компонент не зависит от конкретной CSS-методологии;
  • стили можно менять без изменения JS;
  • один и тот же контроллер работает с разными темами.

Абстрагирование поведения без HTML-дублирования

Для часто повторяющихся паттернов (модальные окна, раскрывающиеся блоки, вкладки) важно избегать копирования логики. Stimulus-контроллеры легко масштабируются на любое количество экземпляров.

<div data-controller="accordion">
  <button data-action="accordion#toggle">Заголовок</button>
  <div data-accordion-target="panel"></div>
</div>

<div data-controller="accordion">
  <button data-action="accordion#toggle">Другой заголовок</button>
  <div data-accordion-target="panel"></div>
</div>

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

Наследование контроллеров

Для более сложных сценариев допускается наследование:

export default class extends BaseDropdownController {
  open() {
    super.open()
    this.trackOpen()
  }
}

Наследование оправдано, когда:

  • базовая логика полностью общая;
  • различия минимальны;
  • нет альтернативы через композицию.

В большинстве случаев предпочтительнее композиция, так как она сохраняет гибкость.

Стандартизация имён и структуры

Переиспользуемость невозможна без соглашений:

  • имя контроллера отражает поведение, а не контекст (toggle, а не headerMenuToggle);
  • targets именуются по роли, а не по тегу;
  • values описывают параметры, а не бизнес-смысл страницы.

Хорошая структура каталогов:

controllers/
  toggle_controller.js
  modal_controller.js
  dropdown_controller.js

Контроллеры не группируются по страницам, а по функциональности.

Тестируемость переиспользуемых компонентов

Изолированный контроллер легко тестировать:

  • минимальный DOM;
  • отсутствие внешних зависимостей;
  • чёткие входные параметры.

Пример подхода:

  • создать DOM-фрагмент;
  • инициализировать Stimulus Application;
  • проверить изменения DOM после вызова методов.

Это напрямую вытекает из принципов переиспользуемости.

Итоговая модель компонента в Stimulus

Переиспользуемый компонент в Stimulus — это:

  • декларативная конфигурация через HTML;
  • чистая логика без знания контекста;
  • композиция вместо жёсткой иерархии;
  • DOM как контракт, а не реализация.

Такая модель позволяет внедрять интерактивность постепенно, не переписывая архитектуру и не теряя контроль над сложностью.