Stimulus — это легковесный JavaScript-фреймворк, предназначенный для
улучшения взаимодействия с DOM без необходимости создавать сложную
архитектуру SPA. Основная единица организации —
контроллер. Контроллер связывает элементы DOM с
поведением через атрибуты data-controller,
data-action и data-target.
Пример базового контроллера:
// controllers/form_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "error"]
validate() {
const value = this.inputTarget.value
if (value.trim() === "") {
this.showError("Поле не должно быть пустым")
} else {
this.clearError()
}
}
showError(message) {
this.errorTarget.textContent = message
this.inputTarget.classList.add("invalid")
}
clearError() {
this.errorTarget.textContent = ""
this.inputTarget.classList.remove("invalid")
}
}
В HTML контроллер подключается следующим образом:
<form data-controller="form">
<input data-form-target="input" data-action="input->form#validate">
<span data-form-target="error" class="error-message"></span>
</form>
Targets — это способ явно указать элементы DOM, с
которыми работает контроллер. Каждый target объявляется в массиве
static targets. Это позволяет избежать поиска элементов
через querySelector и поддерживать чистую структуру
кода.
Actions связывают события DOM с методами
контроллера. Синтаксис event->controller#method
позволяет реагировать на любые события, включая input,
change, blur, submit и
пользовательские события.
Пример множественной валидации:
validateEmail() {
const email = this.emailTarget.value
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!regex.test(email)) {
this.showError("Некорректный email")
} else {
this.clearError()
}
}
Stimulus позволяет задавать динамические значения
через static values, что делает контроллеры
универсальными.
static values = {
minLength: Number
}
validateLength() {
if (this.inputTarget.value.length < this.minLengthValue) {
this.showError(`Минимальная длина: ${this.minLengthValue}`)
} else {
this.clearError()
}
}
HTML:
<input data-form-target="input" data-form-min-length-value="5" data-action="input->form#validateLength">
Для сложных форм с множеством правил рекомендуется разделять методы валидации по типам:
Асинхронная проверка:
async validateUsername() {
const username = this.usernameTarget.value
const response = await fetch(`/api/check_username?username=${username}`)
const data = await response.json()
if (!data.available) {
this.showError("Имя пользователя уже занято")
} else {
this.clearError()
}
}
Stimulus упрощает управление визуальной обратной связью через манипуляции с классами и текстом. Основные подходы:
add() и remove() для классов ошибки.textContent для отображения сообщений.data-action с CSS-анимациями для
плавного появления ошибок..invalid {
border-color: red;
}
.error-message {
color: red;
font-size: 0.9em;
transition: opacity 0.3s;
}
Stimulus поддерживает декларативный подход, когда логика контроллера определяется через HTML-атрибуты:
<input data-form-target="input"
data-form-pattern-value="^\d{10}$"
data-action="input->form#validatePattern">
Контроллер:
static values = { pattern: String }
validatePattern() {
const regex = new RegExp(this.patternValue)
if (!regex.test(this.inputTarget.value)) {
this.showError("Неверный формат")
} else {
this.clearError()
}
}
Контроллеры Stimulus можно наследовать и комбинировать, что позволяет создавать библиотеку общих правил валидации:
import BaseValidationController from "./base_validation_controller"
export default class extends BaseValidationController {
validatePassword() {
super.validateMinLength(8)
super.validateSpecialChars()
}
}
Stimulus не конфликтует с jQuery, React или другими фреймворками. Контроллеры могут использоваться для обогащения существующего HTML без полной переписывания фронтенда, что особенно полезно при постепенной миграции старых приложений.
Пример комплексного контроллера с несколькими target, значениями и асинхронной проверкой:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "error", "username"]
static values = { minLength: Number, pattern: String }
validate() {
this.validateMinLength()
this.validatePattern()
this.validateUsername()
}
validateMinLength() {
if (this.inputTarget.value.length < this.minLengthValue) {
this.showError(`Минимальная длина: ${this.minLengthValue}`)
} else {
this.clearError()
}
}
validatePattern() {
const regex = new RegExp(this.patternValue)
if (!regex.test(this.inputTarget.value)) {
this.showError("Неверный формат")
}
}
async validateUsername() {
const response = await fetch(`/api/check_username?username=${this.usernameTarget.value}`)
const data = await response.json()
if (!data.available) {
this.showError("Имя пользователя занято")
}
}
showError(message) {
this.errorTarget.textContent = message
this.inputTarget.classList.add("invalid")
}
clearError() {
this.errorTarget.textContent = ""
this.inputTarget.classList.remove("invalid")
}
}
Такой подход позволяет создавать универсальные, легко расширяемые контроллеры, обеспечивая как синхронную, так и асинхронную проверку данных с управлением состоянием DOM и минимальным вмешательством в разметку HTML.