Веб-приложения, построенные по принципу SPA (Single Page
Application), используют History API браузера для управления состоянием
URL без полной перезагрузки страницы. Кнопки «Назад» и «Вперед» в
браузере напрямую взаимодействуют с историей переходов, формируемой
через pushState, replaceState и событие
popstate.
Библиотека Page.js абстрагирует работу с History API, обеспечивая декларативный способ описания маршрутов и автоматическую обработку навигации, включая действия пользователя через кнопки браузера.
popstate и его
рольКнопки «Назад» и «Вперед» инициируют событие popstate.
Это событие срабатывает, когда активная запись истории изменяется.
Особенности:
pushState или
replaceStatestate), переданный
ранееPage.js подписывается на это событие и автоматически вызывает соответствующий маршрут.
Внутренний механизм библиотеки:
При переходе через page('/path'):
history.pushStateПри нажатии «Назад» или «Вперед»:
popstateПример базовой настройки:
page('/', showHome);
page('/about', showAbout);
page('/contact', showContact);
page();
При переходе назад с /contact на /about
автоматически вызовется showAbout.
Каждый обработчик получает объект ctx, который
формируется заново при каждом переходе, включая переходы по истории.
page('/user/:id', (ctx) => {
console.log(ctx.params.id);
});
При нажатии «Назад»:
ctx.params пересчитываютсяctx.path соответствует текущему URLctx.state содержит данные из history (если
использовались)ctx.statePage.js позволяет сохранять пользовательские данные в истории:
page('/step1', { step: 1 }, showStep);
page('/step2', { step: 2 }, showStep);
Обработчик:
function showStep(ctx) {
console.log(ctx.state.step);
}
При навигации назад:
state) восстанавливаетсяВ стандартной конфигурации Page.js полностью берет на себя обработку
popstate. Однако иногда требуется кастомное поведение.
Отключение автоматического старта:
page.start({ popstate: false });
Ручная обработка:
window.addEventListener('popstate', (event) => {
console.log('Назад/вперед нажаты');
});
После этого можно вручную управлять вызовами
page.show():
page.show(location.pathname);
Иногда необходимо изменить маршрут без добавления новой записи:
page.replace('/login');
Особенности:
history.replaceStateПри переходе назад Page.js всегда повторно вызывает обработчик маршрута. Это важно учитывать:
Пример:
page('/posts/:id', async (ctx) => {
const post = await fetchPost(ctx.params.id);
render(post);
});
При возврате назад:
Для повышения производительности используются:
1. Кэширование данных
const cache = {};
page('/posts/:id', async (ctx) => {
const id = ctx.params.id;
if (!cache[id]) {
cache[id] = await fetchPost(id);
}
render(cache[id]);
});
2. Сохранение состояния UI
let scrollPositions = {};
page('*', (ctx, next) => {
scrollPositions[ctx.path] = window.scrollY;
next();
});
page('/page', (ctx) => {
renderPage();
const pos = scrollPositions[ctx.path] || 0;
window.scrollTo(0, pos);
});
При навигации назад можно различать направление перехода:
let lastPath = null;
page('*', (ctx, next) => {
ctx.isBack = ctx.path === lastPath;
lastPath = ctx.path;
next();
});
Использование:
page('/page', (ctx) => {
if (ctx.isBack) {
animateBack();
} else {
animateForward();
}
});
Иногда требуется предотвратить возврат:
window.addEventListener('popstate', (event) => {
if (!allowBackNavigation()) {
history.pushState(null, '', location.href);
}
});
Или через Page.js:
page('*', (ctx, next) => {
if (!allowNavigation()) return;
next();
});
При использовании глобальных хранилищ (например, Redux):
popstate состояние маршрута должно
синхронизироватьсяПример:
page('/profile/:id', (ctx) => {
store.dispatch({
type: 'SET_USER',
payload: ctx.params.id
});
});
1. Потеря состояния при возврате
Причина:
2. Дублирование обработчиков
Причина:
page() или
page.start()3. Некорректная работа с replace
Причина:
pushЕсли пользователь вводит URL вручную:
popstateВажно обеспечить одинаковую логику:
page('/dashboard', loadDashboard);
Работает одинаково:
popstatectx.state