Интеграция с Turbo Drive

Turbo Drive отвечает за навигацию без полной перезагрузки страницы, перехватывая переходы по ссылкам и отправку форм. Вместо классического обновления документа происходит замена содержимого <body> с сохранением текущего JavaScript-контекста. Stimulus в этой архитектуре выполняет роль слоя поведения, автоматически подключая и отключая контроллеры при изменении DOM.

Такое разделение обязанностей позволяет отказаться от ручной инициализации JavaScript при каждой навигации и свести логику интерфейса к декларативному описанию в разметке.


Как Turbo Drive влияет на жизненный цикл Stimulus

При обычной загрузке страницы Stimulus сканирует DOM, находит элементы с data-controller и вызывает методы connect. Turbo Drive при переходах заменяет <body>, после чего:

  • старые DOM-узлы удаляются;
  • новые DOM-узлы вставляются;
  • Stimulus автоматически вызывает disconnect для контроллеров удалённых элементов;
  • затем вызывает connect для контроллеров новых элементов.

Дополнительной инициализации не требуется, если код контроллеров не опирается на глобальное состояние, связанное с конкретной страницей.

Ключевой момент: JavaScript-контекст не пересоздаётся, но жизненный цикл контроллеров полностью отрабатывается заново.


События Turbo Drive и их использование в контроллерах

Turbo Drive генерирует ряд событий, отражающих этапы навигации. Они часто используются для тонкой синхронизации Stimulus-логики с процессом перехода.

Основные события:

  • turbo:visit — начало перехода.
  • turbo:before-render — перед подменой <body>.
  • turbo:render — после вставки нового содержимого.
  • turbo:load — завершение навигации и отрисовки.
  • turbo:before-cache — перед сохранением текущей страницы в кэш.

В контексте Stimulus особенно важно событие turbo:before-cache. Оно возникает перед тем, как Turbo Drive сохранит текущий DOM в память для возможного возврата назад.

Пример типового применения — очистка временного состояния:

// example_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.beforeCacheHandler = this.beforeCache.bind(this)
    document.addEventListener("turbo:before-cache", this.beforeCacheHandler)
  }

  disconnect() {
    document.removeEventListener("turbo:before-cache", this.beforeCacheHandler)
  }

  beforeCache() {
    this.element.classList.remove("is-open")
  }
}

Такой подход предотвращает сохранение в кэше «грязного» DOM-состояния.


Кэширование страниц и его последствия

Turbo Drive кэширует посещённые страницы. При возврате назад происходит мгновенное восстановление DOM из памяти без повторного HTTP-запроса.

Особенности этого механизма:

  • connect у контроллеров вызывается заново;
  • turbo:load также срабатывает;
  • состояние элементов сохраняется таким, каким оно было в момент кэширования.

Если контроллер управляет состоянием, которое не должно переживать навигацию (модальные окна, временные подсказки, раскрытые панели), это состояние должно быть сброшено до кэширования.


data-turbo-permanent и Stimulus

Атрибут data-turbo-permanent указывает Turbo Drive не удалять элемент при навигации. Такой элемент сохраняется между страницами и не пересоздаётся.

Последствия для Stimulus:

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

Это удобно для:

  • навигационных панелей;
  • плееров;
  • глобальных уведомлений;
  • элементов, связанных с WebSocket-соединениями.

При использовании data-turbo-permanent особенно важно учитывать, что контроллер фактически становится «долгоживущим» и может накапливать побочные эффекты, если не реализована корректная очистка ресурсов.


Взаимодействие с формами и отправкой данных

Turbo Drive перехватывает отправку форм и выполняет её асинхронно. Stimulus-контроллеры, привязанные к формам, продолжают работать без изменений.

Частый паттерн — управление состоянием формы в процессе отправки:

  • блокировка кнопок;
  • отображение индикаторов загрузки;
  • очистка временных классов при повторном рендере страницы.

Так как после успешной отправки происходит подмена <body>, все визуальные эффекты, связанные с формой, должны либо завершаться до навигации, либо корректно сбрасываться через turbo:before-cache.


Turbo Frames и локальная перерисовка

Turbo Frames позволяют обновлять только часть страницы. В этом случае Stimulus получает ещё более локализованный жизненный цикл:

  • контроллеры внутри frame отключаются и подключаются при обновлении фрейма;
  • контроллеры вне frame не затрагиваются.

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


Глобальные состояния и изоляция логики

Так как Turbo Drive не перезагружает JavaScript-среду, глобальные переменные и синглтоны сохраняются между страницами. В сочетании со Stimulus это требует аккуратного подхода:

  • состояние, зависящее от конкретной страницы, должно находиться внутри контроллера;
  • подписки на события должны сниматься в disconnect;
  • любые таймеры и observer-объекты должны быть явно остановлены.

Stimulus предполагает, что контроллер является полностью самодостаточной единицей, корректно реагирующей на появление и исчезновение элемента в DOM. Turbo Drive делает это предположение критически важным.


Организация кода в приложении с Turbo Drive

На практике связка выглядит следующим образом:

  • Turbo Drive управляет навигацией и обновлением DOM;
  • Stimulus описывает поведение элементов;
  • HTML остаётся источником правды для структуры и декларативных связей.

Такой подход устраняет необходимость в ручной маршрутизации на стороне клиента и в сложных состояниях SPA, сохраняя при этом высокую отзывчивость интерфейса и простоту сопровождения кода.