History API — это встроенный механизм браузера, позволяющий управлять историей навигации без перезагрузки страницы. Библиотека Page.js использует его как фундамент для реализации клиентской маршрутизации в одностраничных приложениях (SPA).
Ключевые возможности History API:
Основные методы:
history.pushState(state, title, url)history.replaceState(state, title, url)popstatePage.js абстрагирует работу с History API, предоставляя удобный интерфейс маршрутизации. При каждом переходе библиотека:
pushState или
replaceStateПример базовой маршрутизации:
page('/', (ctx) => {
console.log('Главная страница');
});
page('/about', (ctx) => {
console.log('О нас');
});
page();
При переходе между маршрутами Page.js автоматически использует
history.pushState.
pushState добавляет новую запись в стек истории.
Сигнатура:
history.pushState(state, title, url);
В контексте Page.js:
page('/user/:id', (ctx) => {
console.log(ctx.params.id);
});
При переходе на /user/42:
replaceState заменяет текущую запись, не добавляя
новую.
history.replaceState(state, title, url);
Использование в Page.js:
page.redirect('/old', '/new');
В этом случае:
/old через кнопку
«назад»Каждая запись истории может содержать произвольный объект состояния.
В Page.js он доступен через ctx.state:
page('/profile', (ctx) => {
console.log(ctx.state);
});
Пример установки состояния:
page.show('/profile', { userId: 123 });
Особенности:
Событие popstate возникает при переходе по истории:
history.back() или
history.forward()Пример:
window.addEventListener('popstate', (event) => {
console.log(event.state);
});
Page.js автоматически подписывается на это событие и:
Page.js предоставляет методы, работающие поверх History API:
page.show(path, state)Добавляет новую запись:
page.show('/dashboard', { fromLogin: true });
Аналог pushState.
page.replace(path, state)Заменяет текущую запись:
page.replace('/dashboard');
Аналог replaceState.
page.redirect(from, to)Перенаправление:
page.redirect('/login', '/dashboard');
Использует replaceState, чтобы избежать лишней записи в
истории.
Главная цель History API — изменение URL без reload.
Page.js перехватывает клики по ссылкам:
<a href="/about">О нас</a>
При клике:
event.preventDefault)page('/about')pushStateHistory API чувствителен к базовому URL приложения.
В Page.js:
page.base('/app');
Теперь маршруты:
page('/home', ...);
будут соответствовать:
/app/home
Это важно при развертывании приложения не в корне домена.
Page.js может работать через History API или через hash
(#).
/about
Плюсы:
Минусы:
/#/about
Использование:
page({ hashbang: true });
Плюсы:
Минусы:
При прямом вводе URL:
https://example.com/about
сервер должен вернуть HTML приложения, иначе возникнет ошибка 404.
Это связано с тем, что:
Типичное решение:
index.html для всех
маршрутовHistory API позволяет сохранять состояние интерфейса:
page('/list', (ctx) => {
if (ctx.state.scrollPosition) {
window.scrollTo(0, ctx.state.scrollPosition);
}
});
Сохранение:
window.addEventListener('beforeunload', () => {
history.replaceState({
scrollPosition: window.scrollY
}, '');
});
Page.js передаёт это состояние в ctx.state.
Браузеры ограничивают размер объекта state:
History API поддерживается:
Page.js автоматически проверяет поддержку.
Page.js перехватывает только внутренние ссылки:
<a href="/about">О нас</a>
Не перехватываются:
<a href="https://external.com">Внешний сайт</a>
<a href="/file.pdf">Файл</a>
<a target="_blank" href="/about">Открыть в новой вкладке</a>
Это реализуется через фильтрацию событий клика.
History API не блокирует выполнение кода, поэтому Page.js поддерживает асинхронные маршруты:
page('/data', async (ctx) => {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
});
URL изменяется сразу, а данные подгружаются позже.
Пример поведения:
page('/step1');
page('/step2');
page('/step3');
История:
step1 → step2 → step3
Кнопка «назад»:
step2popstateМожно использовать нативные методы вместе с Page.js:
history.back();
history.forward();
history.go(-2);
Page.js корректно обработает эти действия через
popstate.
page.replace('/same-page');
Полезно при:
const cache = {};
page('/user/:id', (ctx) => {
if (cache[ctx.params.id]) {
render(cache[ctx.params.id]);
} else {
fetchUser(ctx.params.id).then(data => {
cache[ctx.params.id] = data;
render(data);
});
}
});
History API при этом хранит только навигацию, а не данные.
Правильное использование History API предполагает:
state используется как вспомогательный механизмПример:
/products?page=2
Вместо хранения номера страницы только в state.
Полезные инструменты:
ctxpopstatewindow.addEventListener('popstate', console.log);
pushStatepopstateТакой подход позволяет: