Изоляция контекстов

Page.js — это микробиблиотека для маршрутизации на стороне клиента, позволяющая создавать SPA (Single Page Application) без необходимости полной перезагрузки страниц. Одной из ключевых концепций при работе с Page.js является изоляция контекстов. Контекст здесь означает объект, который передаётся в обработчики маршрутов и содержит состояние текущего перехода: параметры URL, хеш, путь, и дополнительные данные.

Контекст маршрута

Каждый маршрут в Page.js получает объект контекста (ctx), который автоматически создаётся при переходе. Основные свойства этого объекта:

  • ctx.path — полный путь запроса, включая слэш и параметры.
  • ctx.params — объект с динамическими сегментами URL (/user/:id{ id: '123' }).
  • ctx.querystring — строка запроса после ?.
  • ctx.hash — часть URL после #.
  • ctx.state — объект для хранения пользовательских данных между middleware и маршрутами.
  • ctx.canonicalPath — путь без query и hash, полезен для сравнения.

Изоляция контекста означает, что каждое событие маршрутизации получает новый объект ctx, полностью независимый от предыдущих переходов. Это позволяет избежать нежелательного влияния предыдущих состояний на текущий маршрут.

Middleware и последовательность вызова

Page.js использует цепочку middleware для обработки маршрутов. Любой middleware получает текущий контекст и функцию next, которая передаёт управление следующему обработчику:

page((ctx, next) => {
    ctx.state.startTime = Date.now();
    next();
});

page('/user/:id', (ctx) => {
    console.log(`Пользователь ${ctx.params.id}, время начала ${ctx.state.startTime}`);
});
  • ctx полностью уникален для каждой навигации.
  • ctx.state служит контейнером для данных, которые нужны только в рамках текущего маршрута.
  • Вызов next() передаёт управление дальше, сохраняя изоляцию контекста между middleware.

Проблемы с глобальным состоянием

Использование глобальных переменных для хранения состояния маршрутизации нарушает принцип изоляции контекста:

let globalData = {};

page('/user/:id', (ctx) => {
    globalData.userId = ctx.params.id;
});

В этом случае, если произойдёт быстрый переход между маршрутами, данные могут перезаписываться, вызывая непредсказуемое поведение. Изоляция контекстов позволяет хранить состояние локально, в ctx.state, исключая конкуренцию данных.

Вложенные маршруты и подмаршруты

Изоляция контекста особенно важна при организации вложенных маршрутов. Например:

page('/dashboard', (ctx, next) => {
    ctx.state.user = { id: 1, name: 'Admin' };
    next();
});

page('/dashboard/settings', (ctx) => {
    console.log(ctx.state.user.name); // 'Admin'
});

Каждый маршрут получает собственный объект ctx, но middleware верхнего уровня может добавлять данные в ctx.state, которые доступны дочерним маршрутам. Это позволяет разграничивать глобальные и локальные данные, сохраняя при этом изоляцию между переходами.

Передача данных между маршрутами

Page.js не хранит состояние между различными переходами по умолчанию. Для передачи данных используют:

  1. Query-параметры:
page('/search', (ctx) => {
    const query = new URLSearchParams(ctx.querystring).get('q');
    console.log(query);
});
  1. Session Storage / Local Storage — при необходимости сохранить данные вне контекста маршрута.

  2. Сервис или менеджер состояния, если приложение использует централизованное хранение (например, Redux, Zustand). В этом случае важно различать состояние маршрута и глобальное состояние приложения.

Асинхронные операции и контекст

При асинхронных вызовах нужно учитывать, что контекст может устареть при медленных ответах:

page('/data', async (ctx) => {
    const id = ctx.params.id;
    const response = await fetch(`/api/data/${id}`);
    const data = await response.json();
    console.log(data);
});

Если пользователь быстро переходит на другой маршрут, ctx этого запроса остаётся неизменным, что гарантирует корректную привязку данных к конкретному переходу. Использование глобальных переменных в таких сценариях может привести к “утечкам” данных между маршрутами.

Практическая рекомендация

  • Все данные, специфичные для маршрута, хранить в ctx.state.
  • Глобальные данные использовать только для действительно глобальных целей (например, авторизация).
  • Middleware можно использовать для инициализации данных, доступных только в рамках текущего контекста.
  • Избегать прямой модификации URL или window.history вне Page.js, чтобы не нарушить целостность контекста.

Контекст и навигационные события

Page.js автоматически создаёт новый контекст при всех типах переходов:

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