Stimulus — небольшой JavaScript-фреймворк от Basecamp, который строится вокруг уже существующего HTML. Он не создаёт виртуальный DOM и не требует описывать интерфейс в компонентах: вы добавляете к разметке атрибуты data-controller, data-action и data-*-target, а контроллер оживляет поведение. В 2026 году подход остаётся востребованным в проектах на Rails и Hotwire, но Stimulus спокойно работает и со статическим сайтом, и с любым серверным рендерингом.

Где уместен Stimulus

Stimulus решает локальные задачи: раскрыть меню, посчитать символы, отправить форму по изменению, показать модальное окно, синхронизировать два поля. Он не пытается управлять всем состоянием приложения и не заменяет React или Vue там, где нужен сложный клиентский роутинг.

  • HTML остаётся источником правды, а сервер — основным рендерером.
  • Контроллеры подключаются к участку страницы и не мешают друг другу.
  • Нет сборки обязательной: можно начать с import map или бандлера.
  • Хорошо сочетается с Turbo: после навигации контроллеры переподключаются автоматически.

Установка

Для проекта с npm достаточно одного пакета. Дальше выберите способ сборки: Vite, esbuild, webpack или обычный import map.

npm install @hotwired/stimulus
npm install --save-dev vite

В Rails установка автоматизирована, но требует, чтобы приложение уже использовало importmap или jsbundling:

bin/rails stimulus:install

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

import { Application } from "@hotwired/stimulus"
import HelloController from "./hello_controller"

const app = Application.start()
app.register("hello", HelloController)

Первый контроллер

Разметка связывает элемент с контроллером по имени. В контроллере используется класс с методами жизненного цикла. Ниже — минимальный пример приветствия.

<div data-controller="hello">
  <input data-hello-target="name"
         data-action="input->hello#greet">
  <p data-hello-target="output"></p>
</div>
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["name", "output"]

  greet() {
    this.outputTarget.textContent =
      `Привет, ${this.nameTarget.value}`
  }
}

Имя в register должно совпадать с атрибутом data-controller; имя файла обычно делают таким же. Если контроллер не срабатывает, первым делом проверьте консоль: Stimulus пишет предупреждение о незарегистрированном имени.

Targets

Targets дают типизированный доступ к элементам внутри контроллера. Для каждого объявленного имени создаются свойства nameTarget, nameTargets и hasNameTarget.

static targets = ["input", "output", "error"]

update() {
  if (!this.hasErrorTarget) return
  this.errorTarget.hidden = this.inputTarget.value.length > 0
}

Не злоупотребляйте querySelector: targets переживают перерисовку фрагмента и лучше читаются. Если элемент появляется динамически, используйте колбэки inputTargetConnected и inputTargetDisconnected — они вызываются при подключении и отключении цели.

Values

Values передают данные из HTML в контроллер и приводят их к типу. Поддерживаются String, Number, Boolean, Array и Object.

static values = {
  limit: Number,
  url: String,
  open: Boolean
}

connect() {
  this.element.dataset.state =
    this.openValue ? "open" : "closed"
}

В разметке значение задаётся как data-counter-limit-value="280". При изменении атрибута вызывается limitValueChanged; это удобно для реакции на внешние обновления, но не стоит использовать как замену полноценному хранилищу состояния.

Actions и события

Action связывает событие с методом. Формат — событие->контроллер#метод. Модификаторы :prevent, :stop, :self и :once управляют поведением.

<form data-controller="search"
      data-action="submit->search#run">
  <div data-controller="menu">
    <button data-action="click->menu#toggle
                       keydown.esc@window->menu#close">
      Меню
    </button>
  </div>
</form>

@window вешает обработчик на окно, что полезно для закрытия по Escape или глобального клика. Клавиатурные фильтры вроде .enter и .ctrl избавляют от ручных проверок event.key.

Для общения между контроллерами используйте this.dispatch("updated", { detail: { id } }). Событие всплывает от элемента контроллера, поэтому его можно слушать обычным addEventListener или действием другого контроллера.

Outlets

Outlets — это ссылки на другие контроллеры, а не на произвольные элементы. Они нужны, когда один компонент должен вызвать метод другого.

static outlets = ["result"]

search() {
  this.resultOutlets.forEach((outlet) =>
    outlet.show(this.element))
}

В HTML укажите data-search-result-outlet=".result". Селектор ищет элементы в документе, поэтому не делайте его слишком широким. Для необязательной связи проверяйте hasResultOutlet.

Практика: счётчик символов

Соберём поле с ограничением и индикатором. Контроллер обновляет счётчик при вводе и меняет класс, когда лимит превышен.

<form data-controller="counter"
      data-counter-limit-value="280">
  <textarea data-counter-target="input"
    data-action="input->counter#update"></textarea>
  <span data-counter-target="output">0</span>
</form>
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "output"]
  static values = { limit: Number }

  connect() { this.update() }

  update() {
    const size = this.inputTarget.value.length
    this.outputTarget.textContent =
      `${size} / ${this.limitValue}`
    this.outputTarget.classList.toggle(
      "over", size > this.limitValue)
  }
}

Обратите внимание на connect(): он выставляет начальное значение при загрузке страницы и при возврате через Turbo. Если форма приходит из кэша браузера, значение в поле может отличаться от текста в счётчике — тогда обновляйте индикатор по событию pageshow.

Отладка и ограничения

Включите отладочный режим у приложения: в консоли появятся сообщения о подключении контроллеров, срабатывании actions и изменении values.

const app = Application.start()
app.debug = true

Stimulus не выполняет код из строк и не использует eval, поэтому дружит со строгой CSP. Но он не даёт реактивности уровня компонентов: если состояние сложное, придётся вручную синхронизировать DOM и данные. Для тестов используйте Playwright или jsdom: официального набора утилит нет, зато контроллер можно импортировать как обычный модуль.

Не забывайте про доступность: переключатель меню должен менять aria-expanded, модальное окно — удерживать фокус, а скрытые элементы лучше прятать атрибутом hidden, а не только классом.

Итог

Stimulus — практичный выбор, когда HTML уже отрисован сервером, а JavaScript нужен точечно. Он даёт понятную связь разметки и кода через controllers, targets, values и outlets, не навязывая архитектуру приложения. Начните с одного контроллера на странице, проверьте его в отладочном режиме и постепенно выносите повторяющиеся поведения. Если же интерфейс требует сложного клиентского состояния и роутинга, честнее взять полноценный фреймворк, чем бороться с ограничениями лёгкого инструмента.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте