Принципы работы с DOM без виртуализации

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

Контроллеры Stimulus управляют конкретными участками DOM, что позволяет изолировать логику и минимизировать взаимозависимости. Каждый контроллер — это класс JavaScript, который расширяет базовый Controller фреймворка.

import { Controller } from "@hotwired/stimulus"

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

  connect() {
    this.outputTarget.textContent = "Stimulus подключен!"
  }
}

Здесь ключевые моменты:

  • static targets — определяет элементы внутри контроллера, с которыми будет производиться взаимодействие.
  • connect() — метод, который вызывается при инициализации контроллера и привязке его к DOM.

Декларативное связывание элементов

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

  • data-controller — объявляет контроллер на элементе.
  • data-action — связывает события DOM с методами контроллера.
  • data-target — помечает элементы, с которыми контроллер будет работать.

Пример использования:

<div data-controller="greeting">
  <input data-action="input->greeting#update" type="text">
  <span data-greeting-target="output"></span>
</div>
import { Controller } from "@hotwired/stimulus"

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

  update(event) {
    this.outputTarget.textContent = `Привет, ${event.target.value}!`
  }
}

В этом примере Stimulus напрямую управляет DOM, без промежуточного слоя виртуализации: изменение текста происходит на конкретном элементе, привязанном через target.

Работа с событиями

В Stimulus события обрабатываются через декларативные data-action, что упрощает поддержку кода и уменьшает необходимость в явном добавлении слушателей через addEventListener. Формат атрибута:

data-action="событие->контроллер#метод"

Можно привязывать несколько событий к одному методу или использовать несколько методов для одного события:

<button data-action="click->counter#increment mouseover->counter#highlight">
  Нажми меня
</button>
export default class extends Controller {
  static targets = ["display"]

  increment() {
    this.displayTarget.textContent = parseInt(this.displayTarget.textContent) + 1
  }

  highlight() {
    this.displayTarget.classList.add("highlight")
  }
}

Управление состоянием через Targets

Stimulus позволяет логично управлять состоянием интерфейса без необходимости хранить ссылки на DOM вручную. Targets обеспечивают чёткую идентификацию элементов и предотвращают поломку логики при изменении структуры документа.

export default class extends Controller {
  static targets = ["panel", "button"]

  toggle() {
    this.panelTarget.classList.toggle("hidden")
    this.buttonTarget.textContent = this.panelTarget.classList.contains("hidden") ? "Показать" : "Скрыть"
  }
}

Такой подход сохраняет код компактным и декларативным, избавляя от необходимости писать громоздкие селекторы querySelector.

Жизненный цикл контроллера

Контроллеры Stimulus имеют несколько ключевых методов жизненного цикла:

  • connect() — вызывается при подключении контроллера к DOM.
  • disconnect() — вызывается при удалении элемента из DOM.
  • initialize() — вызывается один раз при создании экземпляра контроллера.
  • refresh() — позволяет вручную обновить состояние контроллера при изменении DOM.

Использование этих методов позволяет управлять ресурсами и состоянием элементов, например, добавлять или удалять слушатели событий в нужные моменты.

Работа с данными через values

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

export default class extends Controller {
  static values = { count: Number }

  connect() {
    console.log(this.countValue)
  }

  increment() {
    this.countValue++
  }
}

HTML:

<div data-controller="counter" data-counter-count-value="5">
  <button data-action="click->counter#increment">Увеличить</button>
</div>

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

Асинхронные операции и интеграция с DOM

Stimulus идеально подходит для управления асинхронными событиями, такими как AJAX-запросы или WebSocket-обновления. Изменения DOM происходят только там, где это нужно, без полной перерисовки страницы.

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

  async fetchData() {
    const response = await fetch("/api/items")
    const items = await response.json()
    this.listTarget.innerHTML = items.map(item => `<li>${item.name}</li>`).join("")
  }
}

Принципы работы без виртуализации

  1. Прямое взаимодействие с DOM — элементы изменяются непосредственно, без промежуточной структуры.
  2. Локальная реактивность — контроллер реагирует только на события своих targets.
  3. Минимизация селекторов — использование data-target и data-controller снижает зависимость от классов и id.
  4. Декларативные событияdata-action заменяет явное подключение слушателей через JS.
  5. Синхронизация состояния через values — состояние хранится внутри контроллера, а DOM обновляется только при необходимости.

Stimulus создаёт структуру, в которой DOM остаётся главным источником истины, а логика JavaScript добавляется поверх существующей разметки без виртуализации и сложных слоёв абстракции. Это делает фреймворк особенно подходящим для прогрессивного улучшения интерфейсов на уже существующих страницах.