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

В основе работы Shepherd.js лежит объект тура (Tour), который инкапсулирует текущее состояние прохождения: активный шаг, список шагов, настройки поведения и обработчики событий. Управление состоянием происходит как через API самого тура, так и через встроенную систему событий.

Создание экземпляра:

const tour = new Shepherd.Tour({
  defaultStepOptions: {
    cancelIcon: { enabled: true },
    scrollTo: true
  }
});

Внутри этого объекта поддерживается:

  • текущий активный шаг
  • индекс шага
  • список всех шагов
  • флаг активности тура
  • состояние отмены/завершения

Активный шаг и его отслеживание

Получение текущего шага:

const currentStep = tour.getCurrentStep();

Проверка активности тура:

const isActive = tour.isActive();

Активный шаг — ключевой элемент состояния. Он изменяется при:

  • вызове tour.next()
  • вызове tour.back()
  • переходе через tour.show(id)
  • завершении (complete) или отмене (cancel)

Управление переходами между шагами

Переход к следующему шагу

tour.next();

Возврат к предыдущему

tour.back();

Переход к конкретному шагу

tour.show('step-id');

Каждый переход обновляет внутреннее состояние:

  • индекс текущего шага
  • DOM-рендеринг тултипа
  • позиционирование относительно элемента
  • состояние кнопок

Добавление и удаление шагов во время выполнения

Состояние тура может изменяться динамически.

Добавление шага:

tour.addStep({
  id: 'dynamic-step',
  text: 'Динамически добавленный шаг',
  attachTo: { element: '.dynamic', on: 'bottom' }
});

Удаление шага:

tour.removeStep('dynamic-step');

Важно: если удаляется текущий шаг, Shepherd автоматически пересчитывает состояние и может перейти к следующему доступному шагу.


Перезапуск и сброс состояния

Запуск тура

tour.start();

При запуске:

  • сбрасывается индекс
  • активируется первый шаг
  • устанавливается состояние “активен”

Завершение тура

tour.complete();

Принудительная отмена

tour.cancel();

Разница:

  • complete() — нормальное завершение
  • cancel() — прерывание (например, пользователь закрыл тур)

После любого из этих вызовов:

  • состояние становится неактивным
  • текущий шаг сбрасывается
  • UI удаляется из DOM

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

Shepherd предоставляет событийную модель, позволяющую отслеживать изменения состояния.

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

tour.on('start', () => {});
tour.on('complete', () => {});
tour.on('cancel', () => {});
tour.on('show', (event) => {});
tour.on('hide', (event) => {});

Пример отслеживания текущего шага:

tour.on('show', (event) => {
  console.log(event.step.id);
});

События позволяют:

  • синхронизировать состояние с приложением
  • сохранять прогресс
  • запускать стороннюю логику

Интеграция с внешним состоянием (Redux, Vuex и др.)

Для сложных приложений состояние тура часто выносится во внешний стор.

Пример с Redux-подходом:

tour.on('show', (event) => {
  store.dispatch({
    type: 'SET_TOUR_STEP',
    payload: event.step.id
  });
});

Восстановление состояния:

const savedStep = store.getState().tourStep;

if (savedStep) {
  tour.show(savedStep);
}

Такой подход позволяет:

  • сохранять прогресс пользователя
  • восстанавливать тур после перезагрузки страницы
  • синхронизировать состояние между вкладками

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

Часто требуется изменять поведение тура в зависимости от условий.

Пропуск шагов:

tour.addStep({
  id: 'conditional',
  text: 'Шаг с условием',
  when: {
    show: () => {
      if (!user.isAdmin) {
        tour.next();
      }
    }
  }
});

Динамическое управление переходами:

buttons: [
  {
    text: 'Далее',
    action() {
      if (form.isValid()) {
        return this.next();
      }
    }
  }
]

Асинхронное состояние

Shepherd поддерживает асинхронные операции перед отображением шага.

tour.addStep({
  id: 'async-step',
  text: 'Загрузка данных...',
  beforeShowPromise() {
    return fetch('/api/data')
      .then(res => res.json())
      .then(data => {
        // обновление состояния
      });
  }
});

Шаг не будет показан, пока Promise не завершится.

Это позволяет:

  • дождаться загрузки DOM-элементов
  • получить данные с сервера
  • подготовить UI

Состояние привязки к DOM

Каждый шаг может зависеть от наличия элемента:

attachTo: {
  element: '.selector',
  on: 'bottom'
}

Если элемент отсутствует:

  • шаг может не отобразиться
  • тур может перейти дальше

Решение — контроль состояния DOM:

beforeShowPromise() {
  return new Promise(resolve => {
    const interval = setInterval(() => {
      if (document.querySelector('.selector')) {
        clearInterval(interval);
        resolve();
      }
    }, 100);
  });
}

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

Кнопки шагов также отражают текущее состояние:

buttons: [
  {
    text: 'Назад',
    action: tour.back,
    secondary: true
  },
  {
    text: 'Далее',
    action: tour.next
  }
]

Динамическое изменение:

step.updateStepOptions({
  buttons: [...]
});

Глобальное состояние Shepherd

Shepherd хранит ссылку на текущий активный тур:

Shepherd.activeTour

Использование:

if (Shepherd.activeTour) {
  Shepherd.activeTour.cancel();
}

Это полезно для:

  • предотвращения запуска нескольких туров
  • глобального контроля UI

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

При наличии нескольких туров:

const tours = {
  onboarding: new Shepherd.Tour(),
  advanced: new Shepherd.Tour()
};

Контроль состояния:

function startTour(name) {
  if (Shepherd.activeTour) {
    Shepherd.activeTour.cancel();
  }
  tours[name].start();
}

Сериализация состояния

Для сохранения состояния:

localStorage.setItem('tour-step', tour.getCurrentStep().id);

Восстановление:

const stepId = localStorage.getItem('tour-step');
if (stepId) {
  tour.start();
  tour.show(stepId);
}

Типичные ошибки управления состоянием

1. Потеря состояния при перерендере UI

Решение: хранение состояния вне компонента.

2. Попытка показать шаг до появления элемента

Решение: beforeShowPromise.

3. Конфликт нескольких туров

Решение: контроль через Shepherd.activeTour.

4. Некорректная последовательность шагов

Решение: явное управление show(id).


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

  • хранить состояние тура отдельно от UI
  • использовать события Shepherd как единственный источник истины
  • избегать прямых манипуляций DOM вне Shepherd
  • централизовать запуск/остановку туров
  • учитывать асинхронность интерфейса

Минимальный пример управления состоянием

const tour = new Shepherd.Tour();

tour.addStep({
  id: 'step-1',
  text: 'Первый шаг',
  buttons: [
    {
      text: 'Далее',
      action: tour.next
    }
  ]
});

tour.addStep({
  id: 'step-2',
  text: 'Второй шаг',
  buttons: [
    {
      text: 'Назад',
      action: tour.back
    },
    {
      text: 'Завершить',
      action: tour.complete
    }
  ]
});

tour.on('show', (e) => {
  console.log('Текущий шаг:', e.step.id);
});

tour.start();

Этот пример демонстрирует полный цикл управления состоянием:

  • инициализация
  • переходы
  • отслеживание
  • завершение