Реинициализация и динамическое добавление контроллеров

Stimulus построен вокруг концепции контроллеров, которые связывают HTML-элементы с поведением через data-controller. В стандартной ситуации контроллеры инициализируются один раз при загрузке страницы. Однако в динамических интерфейсах, где DOM может меняться после первоначальной загрузки (например, при подгрузке контента через AJAX или Turbo), возникает необходимость реинициализации существующих контроллеров и подключения новых к элементам, добавленным динамически.

Метод application.load()

Stimulus предоставляет API для явной инициализации контроллеров. Каждый экземпляр приложения Stimulus хранится в объекте Application, который управляет жизненным циклом контроллеров. После добавления нового элемента в DOM с атрибутом data-controller можно вызвать:

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

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

// Динамически добавленный элемент
const newElement = document.createElement("div")
newElement.setAttribute("data-controller", "hello")
document.body.appendChild(newElement)

// Реинициализация
application.load(newElement)

Метод load() инициирует контроллеры только на переданном элементе, что предотвращает повторную инициализацию уже существующих контроллеров на других элементах.

Контроль жизненного цикла

Каждый контроллер Stimulus проходит через следующие стадии:

  1. initialize() — выполняется один раз при создании экземпляра, до привязки к элементу.
  2. connect() — вызывается при добавлении элемента в DOM или при инициализации.
  3. disconnect() — вызывается при удалении элемента из DOM.

При динамическом добавлении элементов важно учитывать, что:

  • initialize() не вызовется повторно для существующих контроллеров.
  • connect() может быть вызван повторно только для новых элементов, если они были зарегистрированы через application.load().

Это поведение гарантирует стабильность состояния контроллеров и предотвращает утечки памяти.

Динамическое добавление контроллеров

Stimulus позволяет добавлять контроллеры после старта приложения, что особенно полезно при модульной загрузке компонентов. Для этого используется метод application.register(name, controllerClass). После регистрации новые контроллеры автоматически инициализируются на элементах с соответствующим data-controller.

Пример добавления контроллера после загрузки страницы:

import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Поздняя регистрация контроллера
import ModalController from "./controllers/modal_controller"
application.register("modal", ModalController)

// Элемент уже находится в DOM
const modalElement = document.querySelector("#dynamic-modal")
// Контроллер автоматически инициализируется на modalElement

Если элемент создаётся динамически до регистрации контроллера, потребуется вызвать application.load(element) после регистрации.

Управление множественными контроллерами

Один элемент может иметь несколько контроллеров, указанных через пробел в data-controller:

<div data-controller="tabs modal"></div>

Stimulus гарантирует, что каждый контроллер будет инициализирован отдельно. Для динамически добавленных элементов порядок действий:

  1. Добавить элемент в DOM.
  2. Зарегистрировать все необходимые контроллеры (если они ещё не зарегистрированы).
  3. Вызвать application.load(newElement) для запуска всех контроллеров на этом элементе.

Обработка событий при динамическом добавлении

При динамическом добавлении элементов важно учитывать события жизненного цикла:

  • connect срабатывает сразу после инициализации контроллера.
  • disconnect — при удалении элемента, что позволяет освобождать ресурсы, останавливать таймеры и отменять подписки на события.

Для элементов, которые добавляются и удаляются многократно, рекомендуется использовать disconnect() для очистки состояний и предотвращения утечек памяти:

export default class extends Controller {
  connect() {
    this.interval = setInterval(() => console.log("tick"), 1000)
  }

  disconnect() {
    clearInterval(this.interval)
  }
}

Рекомендации по архитектуре

  • Минимизировать прямое манипулирование DOM: предпочтительно использовать методы Stimulus для добавления/удаления контроллеров.
  • Использовать уникальные идентификаторы контроллеров для динамически создаваемых элементов, чтобы избежать конфликтов.
  • Разделять логику инициализации и обработку событий, чтобы connect оставался лёгким, а сложные операции выполнялись после полной готовности DOM.

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

  1. Подгрузка контента через Turbo/Fetch: после вставки нового HTML вызвать application.load(newContent).
  2. Модальные окна: при создании окна через JavaScript регистрировать контроллер и инициализировать его на новом элементе.
  3. Таблицы с динамическими строками: каждая новая строка с интерактивными элементами получает свой контроллер через application.load(rowElement).

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