События изменения маршрута

В основе работы библиотеки Page.js лежит механизм отслеживания изменений URL и сопоставления их с обработчиками маршрутов. Каждое изменение маршрута инициирует цепочку событий, которая проходит через несколько этапов: определение маршрута, выполнение middleware, вызов конечного обработчика и возможное прерывание цепочки.

Маршрут в Page.js — это не просто строка URL, а полноценная последовательность функций, каждая из которых получает контекст и управление потоком выполнения.


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

Каждое событие изменения маршрута сопровождается созданием объекта контекста (ctx). Этот объект передаётся через всю цепочку обработчиков и содержит ключевые данные:

  • ctx.path — текущий путь
  • ctx.params — параметры маршрута
  • ctx.querystring — строка запроса
  • ctx.hash — якорь URL
  • ctx.state — состояние истории
  • ctx.title — заголовок страницы (если используется)

Контекст является центральной точкой взаимодействия между обработчиками и позволяет передавать данные между middleware.


Обработчики маршрутов как цепочка middleware

Page.js реализует концепцию middleware, аналогичную серверным фреймворкам. Каждый маршрут может содержать несколько функций:

page('/user/:id', loadUser, showUser);

Каждая функция имеет сигнатуру:

function(ctx, next) {
  // логика
  next();
}
  • ctx — объект контекста
  • next — функция для передачи управления следующему обработчику

Если next() не вызывается, цепочка останавливается.


Событие перехода (Navigation Event)

Переход между маршрутами может происходить несколькими способами:

  1. Клик по ссылке (перехватывается автоматически)
  2. Программный вызов page('/path')
  3. Использование page.show('/path')
  4. Навигация назад/вперёд через History API

При каждом таком действии запускается процесс маршрутизации.


Перехват ссылок

Page.js автоматически перехватывает клики по ссылкам (<a>), если они:

  • ведут на тот же домен
  • не содержат target="_blank"
  • не имеют модификаторов (Ctrl, Shift и т.д.)

При клике:

  1. Отменяется стандартное поведение браузера
  2. Вызывается внутренний механизм маршрутизации
  3. Генерируется новый контекст
  4. Запускается цепочка обработчиков

Вызов маршрута вручную

Программная навигация осуществляется через:

page('/about');

или

page.show('/about');

Разница:

  • page() — универсальный вызов (может регистрировать маршрут или переходить)
  • page.show() — явный переход к маршруту

При вызове:

  • создаётся новый контекст
  • добавляется запись в историю
  • запускается цепочка middleware

Обработка истории браузера

Page.js использует History API (pushState, popstate), что позволяет:

  • изменять URL без перезагрузки страницы
  • реагировать на кнопки «назад» и «вперёд»

Событие popstate автоматически обрабатывается библиотекой:

window.addEventListener('popstate', handler);

При этом:

  • создаётся новый контекст
  • вызываются соответствующие обработчики маршрута

Middleware до и после маршрутов

Page.js поддерживает глобальные middleware:

page('*', middleware);

Такие функции выполняются при каждом переходе, независимо от маршрута.

Пример:

page('*', function(ctx, next) {
  console.log('Переход к:', ctx.path);
  next();
});

Используется для:

  • логирования
  • проверки авторизации
  • инициализации данных

Прерывание цепочки маршрутов

Если middleware не вызывает next(), выполнение останавливается:

page('/admin', function(ctx, next) {
  if (!isAdmin()) return page('/login');
  next();
});

Таким образом реализуется контроль доступа.


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

Page.js поддерживает асинхронные middleware:

page('/data', async function(ctx, next) {
  ctx.data = await fetchData();
  next();
});

Важно:

  • next() вызывается после завершения асинхронной операции
  • отсутствие next() блокирует дальнейшее выполнение

Ошибки в маршрутах

Page.js не имеет встроенной системы обработки ошибок, но можно реализовать её через middleware:

page('*', async function(ctx, next) {
  try {
    await next();
  } catch (err) {
    console.error(err);
    page('/error');
  }
});

Повторный вызов маршрута (redirect)

Перенаправление выполняется через:

page.redirect('/old', '/new');

или вручную:

page('/new');

При редиректе:

  • текущая цепочка прерывается
  • запускается новая маршрутизация

Обработка параметров маршрута

Параметры извлекаются автоматически:

page('/user/:id', function(ctx) {
  console.log(ctx.params.id);
});

При переходе на /user/42:

ctx.params = { id: '42' }

Сравнение маршрутов и порядок выполнения

Маршруты проверяются в порядке регистрации:

page('/user/:id', handler1);
page('/user/settings', handler2);

Проблема: /user/settings может быть перехвачен первым маршрутом.

Решение — регистрировать более специфичные маршруты раньше:

page('/user/settings', handler2);
page('/user/:id', handler1);

Событие завершения маршрута

После выполнения всех middleware:

  • управление возвращается в основной поток
  • DOM уже обновлён (если это делалось в обработчиках)
  • состояние приложения синхронизировано с URL

Работа с состоянием (state)

Page.js позволяет передавать состояние:

page.show('/profile', { userId: 1 });

Доступ:

ctx.state.userId

Состояние сохраняется в History API.


Обновление текущего маршрута

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

page.replace('/current');

Отличие:

  • не добавляет запись в историю
  • обновляет текущий маршрут

Использование exit-обработчиков

Page.js поддерживает обработчики выхода из маршрута:

page.exit('/user/:id', function(ctx) {
  console.log('Выход из профиля');
});

Они вызываются:

  • перед переходом на новый маршрут
  • если текущий маршрут совпадает

Очередность событий при смене маршрута

Полный порядок:

  1. Срабатывает exit текущего маршрута
  2. Создаётся новый ctx
  3. Выполняются глобальные middleware (*)
  4. Выполняются middleware маршрута
  5. Вызывается финальный обработчик
  6. Завершается цепочка

Управление прокруткой

Page.js не управляет скроллом автоматически. Реализация:

page('*', function(ctx, next) {
  window.scrollTo(0, 0);
  next();
});

Отключение перехвата ссылок

Можно отключить автоматический перехват:

page.start({ click: false });

Теперь переходы выполняются только вручную.


Ручной запуск маршрутизации

По умолчанию требуется запуск:

page();

или:

page.start();

Это инициирует:

  • привязку обработчиков
  • обработку текущего URL

Поведение при неизвестных маршрутах

Если маршрут не найден:

page('*', function(ctx) {
  page('/404');
});

Или:

page('*', notFoundHandler);

Кеширование данных между маршрутами

Так как ctx создаётся заново, кеширование реализуется вручную:

const cache = {};

page('/users', function(ctx, next) {
  if (cache.users) {
    ctx.users = cache.users;
    return next();
  }

  fetchUsers().then(users => {
    cache.users = users;
    ctx.users = users;
    next();
  });
});

Повторное использование middleware

Middleware можно выносить:

function auth(ctx, next) {
  if (!loggedIn()) return page('/login');
  next();
}

page('/dashboard', auth, dashboardHandler);

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

Page.js не поддерживает вложенность напрямую, но её можно имитировать:

page('/admin', auth, adminLayout);
page('/admin/users', auth, usersHandler);

Общий middleware (auth) применяется вручную.


Ограничения механизма событий

  • Нет встроенной системы событий (emit/listener)
  • Нет автоматической обработки ошибок
  • Нет реактивности — обновление UI полностью на разработчике

Практическая модель мышления

Смена маршрута в Page.js — это:

  • создание нового контекста
  • последовательное выполнение функций
  • ручное управление потоком через next()
  • отсутствие скрытой магии

Такой подход делает поведение маршрутов предсказуемым и полностью контролируемым.