Stimulus изначально проектировался как минималистичный фреймворк,
поэтому в нём отсутствует собственная система классов, миксинов или
декораторов. Наследование контроллеров реализуется средствами самого
JavaScript — через extends и прототипную цепочку. Это
делает модель прозрачной и предсказуемой, но требует строгой дисциплины
в архитектуре.
Контроллер Stimulus — это класс, наследующийся от
Controller, экспортируемого из пакета
@hotwired/stimulus. Любой пользовательский контроллер может
выступать базовым классом для других контроллеров.
import { Controller } from "@hotwired/stimulus"
export default class BaseController extends Controller {
connect() {
this.log("connected")
}
log(message) {
console.log(`[${this.identifier}] ${message}`)
}
}
Такой контроллер не обязан быть привязан к DOM напрямую. Его задача — инкапсулировать общее поведение.
Производный контроллер наследуется стандартным образом:
import BaseController from "./base_controller"
export default class extends BaseController {
connect() {
super.connect()
this.initializeState()
}
initializeState() {
this.state = {}
}
}
Ключевые моменты:
super.connect() необходимо вызывать вручную, если
базовый класс реализует жизненный цикл.identifier, element, targets,
values автоматически доступны через цепочку
наследования.Stimulus использует фиксированный набор lifecycle-методов:
initializeconnectdisconnectПри наследовании важно понимать порядок вызовов. Stimulus вызывает метод самого производного класса. Если требуется логика базового класса, она должна быть вызвана явно.
initialize() {
super.initialize()
this.setup()
}
Отсутствие вызова super не считается ошибкой фреймворка
и не вызывает предупреждений. Это осознанное решение, позволяющее
полностью контролировать поведение.
Targets в Stimulus объявляются статическим свойством класса:
static targets = ["input"]
При наследовании статические свойства не объединяются автоматически. Если производный класс объявляет свои targets, он полностью переопределяет список.
export default class extends BaseController {
static targets = ["button"]
}
В результате inputTarget из базового класса будет
недоступен.
Для объединения targets используется явное расширение:
export default class extends BaseController {
static targets = [...super.targets, "button"]
}
Этот приём требует, чтобы базовый класс тоже определял
static targets.
Аналогично targets, values объявляются как статическое свойство:
static values = {
url: String,
delay: Number
}
При переопределении static values происходит полная
замена. Для сохранения значений базового класса используется объединение
объектов:
static values = {
...super.values,
mode: String
}
Типы значений, дефолты и кастомные геттеры работают корректно через цепочку наследования, если имена не конфликтуют.
Actions в Stimulus не объявляются в классе, а задаются в HTML через
data-action. Наследование влияет только на доступность
методов.
Если базовый контроллер содержит методы-обработчики, они могут использоваться в производных без дополнительной настройки:
data-action="click->extended#handle"
При этом метод handle может быть определён в базовом
классе.
Переопределение метода в производном классе автоматически изменяет поведение всех actions, указывающих на этот контроллер.
Распространённая архитектурная практика — создание абстрактных контроллеров, не предназначенных для прямого использования в DOM.
Пример:
export default class FormController extends Controller {
submit() {
throw new Error("Method submit() must be implemented")
}
validate() {
return true
}
}
Производные контроллеры реализуют обязательные методы:
export default class extends FormController {
submit() {
// конкретная реализация
}
}
Stimulus не имеет встроенной поддержки абстрактных классов, но этот паттерн хорошо работает при строгом контроле архитектуры.
Базовые контроллеры часто используются как контейнеры для:
export default class BaseController extends Controller {
debounce(fn, delay = 300) {
let timeout
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => fn.apply(this, args), delay)
}
}
}
Все производные контроллеры получают доступ к этим методам без дублирования кода.
Stimulus поощряет неглубокое наследование: один базовый класс и минимальное количество уровней.
Вместо создания сложных иерархий часто используется композиция:
Controllerimport { fetchJSON } from "../services/api"
Этот подход снижает связанность и делает код более модульным, но наследование остаётся удобным инструментом для унификации поведения контроллеров.
Наследование в Stimulus — это не основной механизм расширения, а вспомогательный инструмент. Оно эффективно для:
При строгом соблюдении принципов простоты и прозрачности наследование контроллеров становится мощным архитектурным приёмом без избыточной сложности.