В Page.js управление маршрутами строится вокруг функций-обработчиков, привязанных к определённым URL. Для современных SPA (Single Page Application) часто возникает необходимость загружать данные до того, как будет выполнен переход на новый маршрут. Это обеспечивает плавный пользовательский опыт и предотвращает отображение пустых состояний.
Предзагрузка данных — это процесс асинхронного получения информации до вызова рендеринга страницы. В Page.js она реализуется через middleware-функции и возможности маршрутов.
Page.js поддерживает цепочку middleware, где каждый обработчик
принимает аргументы (ctx, next):
page('/user/:id', loadUserData, renderUserPage);
function loadUserData(ctx, next) {
const userId = ctx.params.id;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
ctx.user = data;
next();
})
.catch(err => {
console.error(err);
ctx.user = null;
next();
});
}
function renderUserPage(ctx) {
if (!ctx.user) {
document.body.innerHTML = '<p>Ошибка загрузки данных</p>';
return;
}
document.body.innerHTML = `<h1>${ctx.user.name}</h1><p>${ctx.user.email}</p>`;
}
Ключевые моменты:
ctx хранит контекст маршрута, включая параметры и любые
данные, которые были предзагружены.fetch или любые
другие промисы.В сложных приложениях может потребоваться загрузка сразу нескольких
ресурсов перед рендерингом. Для этого используют
Promise.all:
function loadDashboardData(ctx, next) {
const userPromise = fetch('/api/currentUser').then(r => r.json());
const statsPromise = fetch('/api/stats').then(r => r.json());
Promise.all([userPromise, statsPromise])
.then(([user, stats]) => {
ctx.user = user;
ctx.stats = stats;
next();
})
.catch(err => {
console.error(err);
ctx.user = null;
ctx.stats = null;
next();
});
}
page('/dashboard', loadDashboardData, renderDashboard);
function renderDashboard(ctx) {
if (!ctx.user || !ctx.stats) {
document.body.innerHTML = '<p>Не удалось загрузить данные</p>';
return;
}
document.body.innerHTML = `
<h1>Привет, ${ctx.user.name}</h1>
<p>Статистика: ${ctx.stats.views} просмотров</p>
`;
}
Особенности такого подхода:
С использованием async/await код становится более
читаемым:
async function loadProduct(ctx, next) {
try {
const res = await fetch(`/api/products/${ctx.params.id}`);
ctx.product = await res.json();
} catch (err) {
console.error(err);
ctx.product = null;
}
next();
}
page('/product/:id', loadProduct, renderProduct);
function renderProduct(ctx) {
if (!ctx.product) {
document.body.innerHTML = '<p>Товар не найден</p>';
return;
}
document.body.innerHTML = `
<h2>${ctx.product.name}</h2>
<p>Цена: ${ctx.product.price}</p>
`;
}
Преимущества async/await:
try/catch.Иногда необходимо предзагружать данные только при определённых условиях, например, при изменении параметра маршрута:
function loadComments(ctx, next) {
if (ctx.query.loadComments === 'true') {
fetch(`/api/posts/${ctx.params.id}/comments`)
.then(res => res.json())
.then(comments => {
ctx.comments = comments;
next();
});
} else {
ctx.comments = [];
next();
}
}
page('/post/:id', loadComments, renderPost);
function renderPost(ctx) {
const commentsHtml = ctx.comments.map(c => `<li>${c.text}</li>`).join('');
document.body.innerHTML = `
<h1>Пост ${ctx.params.id}</h1>
<ul>${commentsHtml}</ul>
`;
}
Выводы из практики:
ctx.query позволяет работать с GET-параметрами для
динамической загрузки.Для ускорения навигации можно сохранять результаты предзагрузки в локальном кэше:
const cache = {};
async function loadProfile(ctx, next) {
const userId = ctx.params.id;
if (cache[userId]) {
ctx.user = cache[userId];
} else {
const res = await fetch(`/api/users/${userId}`);
ctx.user = await res.json();
cache[userId] = ctx.user;
}
next();
}
page('/profile/:id', loadProfile, renderProfile);
Преимущества кэширования:
Предзагрузка данных часто объединяется с глобальными хранилищами состояния, например, Redux или простыми объектами:
const store = {
user: null,
posts: []
};
async function preloadData(ctx, next) {
if (!store.user) {
store.user = await fetch('/api/currentUser').then(r => r.json());
}
if (!store.posts.length) {
store.posts = await fetch('/api/posts').then(r => r.json());
}
ctx.store = store;
next();
}
page('/home', preloadData, renderHome);
function renderHome(ctx) {
const { user, posts } = ctx.store;
document.body.innerHTML = `
<h1>Добро пожаловать, ${user.name}</h1>
<ul>${posts.map(p => `<li>${p.title}</li>`).join('')}</ul>
`;
}
Такой подход гарантирует единое место хранения данных и упрощает повторное использование между различными маршрутами.
Эти методы предзагрузки данных делают Page.js гибким инструментом для создания SPA с эффективной и быстрой навигацией, минимизируя задержки и улучшая взаимодействие с пользователем.