Класс UniversalRouter

Universal Router — это минималистичная, гибкая библиотека маршрутизации для JavaScript, работающая как на клиентской, так и на серверной стороне. Она строится вокруг концепции маршрутных объектов и позволяет управлять переходами между страницами или состояниями приложения с помощью асинхронных обработчиков.

Маршрут представляет собой объект с ключами path, action и необязательными children. Основное назначение маршрута — сопоставление URL с соответствующей функцией обработки.

const routes = [
  {
    path: '/home',
    action: () => 'Главная страница'
  },
  {
    path: '/about',
    action: () => 'О сайте'
  }
];

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

Для инициализации маршрутизатора используется конструктор UniversalRouter, которому передается массив маршрутов и опциональная конфигурация.

import UniversalRouter from 'universal-router';

const router = new UniversalRouter(routes);

Метод router.resolve(context) принимает контекст (обычно объект с информацией о текущем URL) и возвращает результат вызова соответствующей функции action. Этот метод возвращает промис, что позволяет использовать асинхронные операции, такие как запросы к API перед отображением страницы.

router.resolve({ pathname: '/home' }).then(result => {
  console.log(result); // 'Главная страница'
});

Структура маршрутов

Маршруты могут быть вложенными, что позволяет организовывать сложные иерархии страниц. Вложенные маршруты задаются с помощью свойства children.

const routes = [
  {
    path: '/users',
    children: [
      {
        path: '/:id',
        action: ({ params }) => `Профиль пользователя ${params.id}`
      },
      {
        path: '/new',
        action: () => 'Создание нового пользователя'
      }
    ]
  }
];

Параметры маршрутов

Параметры URL определяются с помощью двоеточия (:). Universal Router автоматически извлекает значения параметров и передает их в объект params внутри функции action.

const routes = [
  {
    path: '/posts/:postId',
    action: ({ params }) => `Пост с ID: ${params.postId}`
  }
];

router.resolve({ pathname: '/posts/42' }).then(console.log); // 'Пост с ID: 42'

Поддерживаются также опциональные параметры и регулярные выражения:

{
  path: '/products/:id(\\d+)?',
  action: ({ params }) => params.id ? `Продукт ${params.id}` : 'Все продукты'
}

Асинхронные обработчики и middleware

Функции action могут быть асинхронными, что позволяет выполнять любые операции перед возвращением результата. Для промежуточной обработки URL или проверки условий можно использовать цепочку вызовов через middleware-подход.

const router = new UniversalRouter(routes, {
  context: { user: { isAuthenticated: true } }
});

router.resolve({ pathname: '/admin' }, { next: true }).then(result => {
  console.log(result);
});

Middleware может перехватывать переход и вызывать next() для передачи управления следующему обработчику. Это полезно для реализации аутентификации, логирования или динамической загрузки данных.

const routes = [
  {
    path: '/admin',
    action: async ({ next, context }) => {
      if (!context.user.isAuthenticated) {
        return 'Доступ запрещен';
      }
      return next();
    },
    children: [
      {
        path: '/dashboard',
        action: () => 'Панель администратора'
      }
    ]
  }
];

Управление URL и навигацией

Universal Router не навязывает конкретный способ управления историей браузера, но часто используется совместно с History API или библиотеками, такими как history.

import { createBrowserHistory } from 'history';
const history = createBrowserHistory();

history.listen(({ location }) => {
  router.resolve({ pathname: location.pathname }).then(content => {
    document.body.textContent = content;
  });
});

history.push('/home');

Такой подход обеспечивает полный контроль над навигацией, позволяет реализовать SPA с асинхронной загрузкой контента и динамическим изменением состояния URL.

Особенности и преимущества

  • Гибкость: поддержка асинхронных функций и middleware позволяет строить сложные сценарии маршрутизации.
  • Легковесность: минимальный размер и отсутствие жесткой привязки к конкретной платформе.
  • Универсальность: одинаково хорошо работает на сервере и в браузере, что упрощает SSR (Server-Side Rendering).
  • Динамическая маршрутизация: возможность вычислять маршруты на лету через функции и промисы.

Расширенные возможности

Маршруты с функцией load

Иногда нужно загружать модули динамически, не включая их в основной бандл. Для этого используется функция load, которая возвращает модуль с маршрутом или компонентом.

{
  path: '/settings',
  load: async () => import('./settings.js').then(m => m.default)
}

Обработка ошибок

Universal Router позволяет обрабатывать ошибки через объект context или через стандартные механизмы try/catch.

router.resolve({ pathname: '/unknown' }).catch(err => {
  console.error('Маршрут не найден', err);
});

Настройка контекста

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

const context = { user: { role: 'admin' } };
router.resolve({ pathname: '/admin' }, { context });

Итоговый подход к проектированию

При работе с Universal Router рекомендуется:

  • Организовывать маршруты и вложенные маршруты в виде дерева объектов.
  • Использовать асинхронные функции action для загрузки данных перед рендерингом.
  • Применять middleware для авторизации, логирования и предварительной обработки URL.
  • Использовать динамическую загрузку модулей через load для оптимизации SPA.
  • Передавать общий контекст через объект context, чтобы избежать глобальных переменных.

Universal Router обеспечивает мощную и гибкую маршрутизацию, позволяя создавать как простые, так и сложные SPA и SSR-приложения без лишнего кода и зависимости от фреймворков.