Работа с History API

Для работы с History API в Universal Router необходимо использовать объект history браузера. Обычно применяются методы pushState, replaceState и событие popstate для отслеживания навигации. Universal Router не имеет встроенной зависимости от History API, но интеграция реализуется через пользовательский объект, передаваемый в маршрутизатор.

Пример создания маршрутизатора с использованием History API:

import UniversalRouter from 'universal-router';

const routes = [
  { path: '/', action: () => 'Главная страница' },
  { path: '/about', action: () => 'О нас' },
  { path: '/contact', action: () => 'Контакты' },
];

const router = new UniversalRouter(routes);

function navigate(path) {
  history.pushState({}, '', path);
  router.resolve({ pathname: path }).then(result => {
    document.getElementById('app').innerText = result;
  });
}

window.addEventListener('popstate', () => {
  router.resolve({ pathname: window.location.pathname }).then(result => {
    document.getElementById('app').innerText = result;
  });
});

Ключевые моменты:

  • Метод pushState добавляет новый элемент в историю браузера без перезагрузки страницы.
  • Событие popstate срабатывает при нажатии кнопок «Назад» или «Вперед».
  • router.resolve() принимает объект с полем pathname, что позволяет Universal Router определять текущий маршрут.

Асинхронные маршруты и History API

Universal Router поддерживает асинхронные функции в action, что позволяет загружать данные динамически при навигации:

const routes = [
  {
    path: '/user/:id',
    async action(context) {
      const response = await fetch(`/api/users/${context.params.id}`);
      const user = await response.json();
      return `Пользователь: ${user.name}`;
    },
  },
];

Здесь context.params.id автоматически извлекается из URL. При переходе через pushState или popstate Universal Router корректно вызывает асинхронный action и возвращает результат.


Обработка базового пути (base path)

Если приложение размещается не в корне сайта, нужно учитывать базовый путь. Universal Router позволяет использовать префикс при разрешении маршрута:

const basePath = '/app';

function navigate(path) {
  const fullPath = basePath + path;
  history.pushState({}, '', fullPath);
  router.resolve({ pathname: fullPath }).then(render);
}

window.addEventListener('popstate', () => {
  const path = window.location.pathname;
  router.resolve({ pathname: path }).then(render);
});

Префикс автоматически добавляется при построении URL и при разрешении маршрутов. Это важно для SPA, размещённых в подкаталогах.


Динамическая замена состояния без перезагрузки

Методы pushState и replaceState позволяют обновлять URL без перезагрузки. Разница между ними:

  • pushState добавляет новый элемент в историю, создавая точку возврата.
  • replaceState заменяет текущий элемент, не создавая новой точки возврата.

Пример использования replaceState с Universal Router:

function updateQuery(query) {
  const url = new URL(window.location);
  url.searchParams.set('q', query);
  history.replaceState({}, '', url);
  router.resolve({ pathname: url.pathname, query: url.searchParams }).then(render);
}

Это полезно для фильтров, поисковых запросов и других интерактивных элементов без создания лишних записей в истории.


Управление переходами с guard-функциями

Universal Router позволяет реализовать логику проверки перед переходом. При работе с History API это особенно актуально для SPA:

const routes = [
  {
    path: '/protected',
    async action(context) {
      if (!context.user) {
        navigate('/login');
        return;
      }
      return 'Доступ разрешён';
    },
  },
];

Такой подход позволяет интегрировать проверку авторизации и другие условия до рендеринга маршрута, сохраняя совместимость с History API.


Интеграция с событиями браузера

History API предоставляет событие popstate, которое важно для синхронизации состояния приложения с URL:

window.addEventListener('popstate', event => {
  router.resolve({ pathname: window.location.pathname }).then(render);
});
  • event.state содержит объект состояния, переданный в pushState или replaceState.
  • Использование этого объекта позволяет хранить сложные данные состояния маршрута, например, выбранные фильтры или текущую страницу пагинации.

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

  • Всегда использовать router.resolve() при изменении URL через History API, чтобы Universal Router корректно обработал маршрут.
  • Асинхронные действия маршрутов должны возвращать промисы, что позволяет подгружать данные без блокировки интерфейса.
  • При работе с подкаталогами всегда учитывать basePath для корректной генерации URL.
  • Для управления историей использовать комбинацию pushState и replaceState, чтобы контролировать точки возврата.
  • Проверки и guard-функции помогают предотвратить нежелательные переходы и обеспечить безопасность SPA.

Обработка 404 и fallback маршрутов

Universal Router поддерживает маршрут по умолчанию для всех неопределённых путей. При использовании History API это важно для корректного отображения ошибок:

const router = new UniversalRouter([
  { path: '/', action: () => 'Главная' },
  { path: '(.*)', action: () => 'Страница не найдена' },
]);

Маршрут (.*) ловит все URL, которые не соответствуют другим маршрутам, позволяя показать страницу 404 без перезагрузки.


Настройка навигации с query-параметрами

Query-параметры можно передавать в resolve() через объект query:

function navigateWithQuery(path, query) {
  const url = new URL(window.location.origin + path);
  Object.keys(query).forEach(key => url.searchParams.set(key, query[key]));
  history.pushState({}, '', url);
  router.resolve({ pathname: url.pathname, query: url.searchParams }).then(render);
}
  • context.query в маршруте содержит все query-параметры, что позволяет динамически строить страницы на их основе.
  • Это удобно для фильтров, поиска и пагинации в SPA.

Эти возможности делают Universal Router гибким инструментом для построения одностраничных приложений с полной поддержкой History API и динамической навигации.