Состояние в одностраничных приложениях (SPA) отражает текущий набор данных, от которого зависит отображение интерфейса и поведение приложения. В контексте маршрутизации Page.js состояние включает:
Page.js не предоставляет встроенного менеджера состояния, однако он активно участвует в его синхронизации, поскольку маршруты напрямую связаны с URL — одним из ключевых источников состояния.
URL в SPA выступает как источник истины для навигационного состояния.
В Page.js это реализуется через обработчики маршрутов, которые получают
объект ctx:
page('/user/:id', (ctx) => {
console.log(ctx.params.id);
});
Здесь ctx.params.id — часть состояния, извлечённая из
URL. Синхронизация заключается в том, чтобы:
ctxctx.path — полный путьctx.params — параметры маршрутаctx.querystring — строка запросаctx.state — пользовательское состояние (History
API)Page.js использует History API браузера (pushState,
replaceState) для управления навигацией без перезагрузки
страницы.
page('/profile', (ctx) => {
console.log(ctx.state);
});
page.show('/profile', { user: 'Alex' });
Здесь второй аргумент page.show() становится частью
ctx.state.
Page.js не управляет состоянием приложения, поэтому используются внешние решения:
const store = {
user: null
};
page('/login', (ctx) => {
store.user = { name: 'Alex' };
page.redirect('/dashboard');
});
page('/dashboard', (ctx) => {
console.log(store.user);
});
Недостаток — отсутствие реактивности и централизованного контроля.
Маршруты часто требуют загрузки данных с сервера. Важно синхронизировать:
Page.js поддерживает цепочку middleware:
function loadUser(ctx, next) {
fetch(`/api/user/${ctx.params.id}`)
.then(res => res.json())
.then(user => {
ctx.user = user;
next();
});
}
page('/user/:id', loadUser, (ctx) => {
renderUser(ctx.user);
});
Здесь состояние (ctx.user) синхронизируется до
рендеринга.
Если пользователь быстро переключает маршруты:
Решение:
let currentRequest = null;
function loadUser(ctx, next) {
if (currentRequest) currentRequest.abort();
const controller = new AbortController();
currentRequest = controller;
fetch(`/api/user/${ctx.params.id}`, { signal: controller.signal })
.then(res => res.json())
.then(user => {
ctx.user = user;
next();
})
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
}
UI должен отражать текущее состояние маршрута:
page('*', (ctx, next) => {
document.querySelectorAll('a').forEach(link => {
link.classList.toggle('active', link.pathname === ctx.path);
});
next();
});
page('/products', (ctx) => {
const params = new URLSearchParams(ctx.querystring);
const category = params.get('category');
renderProducts(category);
});
Обновление состояния:
page.show('/products?category=books');
Состояние должно обновляться в обе стороны:
function updateFilter(category) {
page.show(`/products?category=${category}`);
}
Теперь:
Для сложных интерфейсов (например, dashboard) состояние может быть составным:
/dashboard?tab=analytics&range=month
Обработка:
page('/dashboard', (ctx) => {
const params = new URLSearchParams(ctx.querystring);
const tab = params.get('tab') || 'overview';
const range = params.get('range') || 'week';
renderDashboard({ tab, range });
});
Повторные переходы между маршрутами могут требовать повторной загрузки данных. Для оптимизации используется кэш.
const cache = {};
function loadUser(ctx, next) {
const id = ctx.params.id;
if (cache[id]) {
ctx.user = cache[id];
return next();
}
fetch(`/api/user/${id}`)
.then(res => res.json())
.then(user => {
cache[id] = user;
ctx.user = user;
next();
});
}
Формы могут изменять состояние маршрута:
document.querySelector('#search').addEventListener('submit', (e) => {
e.preventDefault();
const query = e.target.elements.q.value;
page.show(`/search?q=${encodeURIComponent(query)}`);
});
Обработчик:
page('/search', (ctx) => {
const params = new URLSearchParams(ctx.querystring);
const q = params.get('q');
renderSearch(q);
});
Иногда требуется предотвратить переход при несохранённых данных.
let isDirty = true;
page('/edit', (ctx, next) => {
if (isDirty && !confirm('Есть несохранённые изменения')) {
return;
}
next();
});
При использовании React, Vue или Svelte Page.js обычно интегрируется как слой маршрутизации.
page('/user/:id', (ctx) => {
ReactDOM.render(<User id={ctx.params.id} />, root);
});
Состояние:
Так как URL — основной источник состояния, при перезагрузке:
Однако ctx.state (History API) не сохраняется между
перезагрузками. Для этого используется:
localStorage.setItem('user', JSON.stringify(user));
const user = JSON.parse(localStorage.getItem('user'));
Распространённые проблемы:
Состояние полностью определяется маршрутом:
Пример:
/products?page=2 — в URLПример:
ctx.stateСинхронизация состояния в Page.js строится вокруг идеи: URL управляет приложением, а маршруты становятся точками консолидации данных, интерфейса и логики.