Управление состоянием при переходах

В Stimulus состояние — это не глобальное хранилище данных и не абстрактный слой поверх приложения. Оно локально, привязано к DOM и контроллерам, и существует ровно столько, сколько существует соответствующий фрагмент разметки. Управление состоянием при переходах напрямую связано с жизненным циклом контроллеров, механизмом values, работой с событиями и сохранением данных между изменениями DOM.

Под переходами понимаются:

  • переходы между страницами (в том числе при использовании Turbo),
  • динамическая подмена частей DOM,
  • смена представлений внутри одного интерфейса,
  • скрытие и повторное отображение элементов.

Жизненный цикл контроллера и его влияние на состояние

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

  • initialize() — вызывается один раз при создании экземпляра контроллера;
  • connect() — вызывается каждый раз, когда контроллер связывается с DOM;
  • disconnect() — вызывается при удалении элемента из DOM.

Состояние, хранящееся в свойствах экземпляра контроллера, уничтожается при disconnect. Это ключевой момент при проектировании переходов: любые данные, которые должны пережить удаление DOM-узла, не могут храниться только в памяти контроллера.

export default class extends Controller {
  connect() {
    this.counter = 0
  }

  increment() {
    this.counter++
  }
}

При любом переходе, приводящем к пересозданию элемента, counter будет сброшен.

Использование values для сериализуемого состояния

Механизм values предоставляет декларативный способ хранения состояния, привязанного к DOM через data-* атрибуты. Такие значения автоматически восстанавливаются при повторном подключении контроллера.

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

  next() {
    this.stepValue++
  }
}
<div data-controller="wizard"
     data-wizard-step-value="1">
</div>

Ключевые свойства values:

  • значение хранится в DOM, а не в памяти JS;
  • поддерживаются типы (String, Number, Boolean, Array, Object);
  • автоматически синхронизируется при изменении.

Это основной инструмент сохранения состояния при переходах, не требующий внешних хранилищ.

Состояние при Turbo-переходах

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

Особенности:

  • Turbo Drive кэширует HTML, но не состояние JS;
  • Stimulus контроллеры всегда инициализируются заново;
  • значения из values сохраняются, если они присутствуют в HTML.

Для устойчивого состояния при переходах:

  • критичные данные должны быть сериализованы в HTML;
  • временные данные допускается терять;
  • сложное состояние следует восстанавливать из sessionStorage или localStorage.
connect() {
  const saved = sessionStorage.getItem("wizard-step")
  if (saved) this.stepValue = Number(saved)
}

stepValueChanged(value) {
  sessionStorage.setItem("wizard-step", value)
}

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

При использовании Turbo Frames или ручной подмены HTML через fetch возможны ситуации, когда часть DOM обновляется, а часть остаётся.

Варианты поведения:

  • контроллер полностью отключается и подключается заново;
  • контроллер остаётся, но его targets меняются;
  • контроллер остаётся без изменений.

Для устойчивости состояния:

  • не полагаться на наличие targets в initialize;
  • проверять наличие элементов в connect;
  • реагировать на изменения targets через targetConnected и targetDisconnected.
static targets = ["item"]

itemTargetConnected(element) {
  // восстановление состояния для нового элемента
}

Разделение состояния и представления

Распространённая ошибка — хранение состояния исключительно в DOM-классах или текстовом содержимом. При переходах это приводит к рассинхронизации.

Рекомендуемая схема:

  • состояние хранится в values или внешнем хранилище;
  • представление обновляется в реакциях на изменение состояния;
  • прямое чтение DOM используется минимально.
statusValueChanged(value) {
  this.element.classList.toggle("is-active", value === "active")
}

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

Координация нескольких контроллеров при переходах

При сложных интерфейсах состояние может быть распределено между несколькими контроллерами. При переходах важно избегать скрытых зависимостей.

Подходы:

  • обмен через DOM-события;
  • чтение общего состояния из data-* атрибутов родительского элемента;
  • использование одного «источника истины» в виде контейнерного контроллера.
this.dispatch("change", { detail: { step: this.stepValue } })

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

Восстановление состояния после анимационных переходов

При переходах с анимациями элементы часто временно удаляются или скрываются. В таких случаях:

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

Практика:

  • сохранять состояние до начала перехода;
  • использовать CSS-классы вместо удаления DOM;
  • при необходимости — задерживать удаление элемента.
disconnect() {
  this.saveState()
}

Антипаттерны при управлении состоянием

  • хранение состояния только в свойствах контроллера;
  • использование глобальных переменных;
  • чтение состояния из DOM без явного источника;
  • логика восстановления, разбросанная по нескольким методам;
  • привязка состояния к визуальным классам.

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

Итоговая модель работы со состоянием при переходах

  • жизненный цикл контроллера определяет время жизни состояния;
  • values — основной инструмент для переживания переходов;
  • Turbo не сохраняет JS-состояние;
  • состояние должно быть сериализуемым и восстанавливаемым;
  • логика восстановления должна быть предсказуемой и локализованной.

Такой подход обеспечивает стабильную работу интерфейса при любых переходах, не нарушая философию Stimulus и сохраняя простоту кода.