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

В основе маршрутизации в Page.js лежит использование API истории браузера — history.pushState, history.replaceState и обработка события popstate. Это позволяет управлять URL без перезагрузки страницы и синхронизировать состояние интерфейса с адресной строкой.

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


Добавление записей в стек: pushState

При обычной навигации через Page.js используется метод pushState. Он добавляет новую запись в стек истории:

page('/about');

Внутри библиотеки это приводит к вызову:

history.pushState(state, title, path);

Ключевые особенности:

  • Создаётся новая запись в истории
  • Пользователь может вернуться назад через кнопку браузера
  • URL изменяется без перезагрузки страницы

Page.js автоматически формирует объект state, содержащий информацию о текущем маршруте, параметрах и контексте.


Замена текущего состояния: replaceState

Иногда требуется изменить текущую запись, не добавляя новую. Для этого используется replaceState.

В Page.js это делается через опцию:

page('/login', { replace: true });

или программно:

page.replace('/login');

Поведение:

  • Текущая запись в стеке заменяется
  • Кнопка “Назад” не вернёт к предыдущему URL
  • Используется для редиректов и корректировки URL

Типичные сценарии:

  • Перенаправление после авторизации
  • Удаление временных параметров из URL
  • Исправление некорректного маршрута

Обработка переходов назад и вперёд

При нажатии пользователем кнопок “Назад” или “Вперёд” браузер генерирует событие popstate. Page.js автоматически подписывается на это событие и вызывает соответствующий маршрут.

window.addEventListener('popstate', handler);

Внутри Page.js:

  • извлекается состояние из history.state
  • определяется соответствующий маршрут
  • вызывается цепочка middleware

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

  • popstate не вызывает pushState
  • переходы не создают новые записи в истории
  • состояние берётся из ранее сохранённых данных

Контекст состояния (state object)

Каждое состояние, добавляемое в стек, содержит объект state. Page.js использует его для хранения информации о маршруте:

{
  path: '/users/42',
  params: { id: '42' },
  querystring: '',
  title: '',
  state: {}
}

Назначение:

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

Дополнительно можно передавать пользовательские данные:

page('/profile', { userId: 123 });

Программная навигация

Page.js предоставляет API для управления стеком истории:

Переход с добавлением записи

page('/dashboard');

Замена текущей записи

page.replace('/dashboard');

Редирект

page.redirect('/home');

redirect фактически вызывает replaceState, чтобы избежать лишних записей в истории.


Управление историей при middleware

Page.js поддерживает цепочки middleware, которые могут влиять на переход:

page('/admin', authMiddleware, adminHandler);

Если middleware выполняет редирект:

function authMiddleware(ctx, next) {
  if (!ctx.user) {
    page.redirect('/login');
  } else {
    next();
  }
}

Влияние на стек:

  • текущий маршрут не добавляется
  • происходит замена URL
  • предотвращается “засорение” истории

Кеширование и повторное использование состояний

Браузер сохраняет состояние в history.state. При возврате назад Page.js получает уже существующий объект.

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

  • избежать повторной загрузки данных
  • восстановить UI без лишних запросов
  • ускорить навигацию

Однако важно учитывать:

  • состояние должно быть сериализуемым
  • объём данных ограничен браузером

Синхронизация URL и состояния приложения

Page.js обеспечивает двустороннюю связь:

  1. URL → состояние

    • при вводе URL вручную
    • при переходах назад/вперёд
  2. Состояние → URL

    • при вызове page()
    • при редиректах

Это гарантирует:

  • корректную работу закладок
  • поддержку обновления страницы
  • SEO-дружелюбность (при серверной поддержке)

Ограничения и особенности браузеров

Работа со стеком истории имеет ряд ограничений:

  • Максимальный размер state ограничен
  • Некоторые браузеры (особенно старые) некорректно обрабатывают popstate
  • pushState не вызывает событие popstate

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


Работа с hash-навигацией

Если используется режим с hash (#/route), стек истории работает иначе:

page({ hashbang: true });

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

  • используется location.hash
  • не задействуется pushState
  • поддержка старых браузеров

Недостатки:

  • менее чистые URL
  • ограниченные возможности SEO

Управление историей при вложенных маршрутах

При вложенной маршрутизации каждый переход создаёт отдельную запись:

page('/users', usersList);
page('/users/:id', userDetail);

Переход:

/users → /users/42

Добавляет новую запись в стек.

Если требуется избежать этого:

page.replace('/users/42');

Отладка и анализ истории

Для анализа поведения можно использовать:

console.log(history.length);
console.log(history.state);

А также отслеживать события:

window.addEventListener('popstate', (event) => {
  console.log(event.state);
});

Это помогает:

  • выявлять лишние переходы
  • контролировать стек
  • оптимизировать UX

Типичные ошибки

1. Лишние записи в истории

page('/step1');
page('/step2');
page('/step3');

Решение:

page.replace('/step2');

2. Потеря состояния

  • хранение сложных объектов
  • несериализуемые данные

3. Некорректные редиректы

  • использование page() вместо page.redirect()

4. Игнорирование popstate

  • ручное вмешательство в history API без синхронизации с Page.js

Практики эффективного управления стеком

  • использовать replace для промежуточных шагов
  • минимизировать количество записей
  • хранить только необходимые данные в state
  • учитывать пользовательский сценарий навигации
  • тестировать поведение кнопок “Назад/Вперёд”

Взаимодействие с сервером

При прямом открытии URL сервер должен корректно обрабатывать маршрут:

GET /users/42

Иначе при обновлении страницы возникнет ошибка.

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

* → index.html

Это гарантирует:

  • сохранение состояния URL
  • корректную загрузку SPA
  • согласованность истории

Глубокая интеграция с History API

Page.js не скрывает полностью history, что позволяет при необходимости:

history.pushState({ custom: true }, '', '/custom');

Но при этом важно вручную инициировать обработку маршрута:

page.show('/custom');

Поведение при обновлении страницы

При перезагрузке:

  • стек истории сохраняется
  • текущее состояние теряется (если не сериализовано)
  • приложение инициализируется заново

Page.js повторно разбирает URL и вызывает соответствующий маршрут.


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

History API не управляет прокруткой автоматически. Page.js также не вмешивается в это поведение.

Рекомендуется вручную контролировать:

window.scrollTo(0, 0);

или сохранять позицию в state.


Расширенные сценарии

Модальные окна через историю:

page('/modal', showModal);

Закрытие:

history.back();

Многошаговые формы:

  • шаги через replaceState
  • финальный шаг через pushState

Навигация с состоянием:

page('/search', { query: 'js' });

Внутреннее устройство Page.js

При каждом вызове page():

  1. Формируется контекст (ctx)
  2. Вызывается pushState или replaceState
  3. Запускается middleware-цепочка
  4. Рендерится представление

При popstate:

  1. Извлекается state
  2. Восстанавливается ctx
  3. Повторно вызываются обработчики

Управление переходами без изменения URL

Иногда требуется изменить состояние приложения без изменения URL. Page.js не предоставляет прямого API для этого, но можно:

history.replaceState(newState, '', location.pathname);

Роль стека истории в UX

Грамотное управление стеком напрямую влияет на:

  • предсказуемость навигации
  • удобство возврата
  • ощущение “нативности” приложения

Неправильная работа приводит к:

  • “сломленной” кнопке назад
  • дублирующимся страницам
  • путанице в навигации

Сравнение pushState и replaceState

Метод Добавляет запись Влияет на “Назад” Использование
pushState Да Да Обычная навигация
replaceState Нет Нет Редиректы, корректировки

Контроль длины истории

console.log(history.length);

Позволяет отслеживать:

  • количество переходов
  • потенциальные проблемы с навигацией

Итеративная оптимизация

При разработке крупных SPA важно регулярно:

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

Page.js предоставляет достаточно гибкости для точного контроля, но требует внимательного подхода к архитектуре маршрутизации.