Использование с React

Universal Router — это легковесная библиотека для маршрутизации, которая позволяет создавать динамические маршруты и обрабатывать их через функцию resolve. При работе с React основной задачей становится соединение маршрутов с компонентами, обеспечивая динамическое отображение контента на основе URL.

Настройка маршрутов

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

import UniversalRouter from 'universal-router';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import UserPage from './pages/UserPage';

const routes = [
  {
    path: '/',
    action: () => <HomePage />
  },
  {
    path: '/about',
    action: () => <AboutPage />
  },
  {
    path: '/user/:id',
    action: ({ params }) => <UserPage userId={params.id} />
  }
];

const router = new UniversalRouter(routes);

В path можно использовать динамические сегменты, обозначенные через :param, которые Universal Router автоматически передаёт в объект params.

Обработка навигации

В React переходы между маршрутами обычно реализуются через history API или через обёртки, интегрированные с библиотеками типа react-router-dom. С Universal Router можно управлять навигацией вручную, вызывая router.resolve с нужным URL.

import { useState, useEffect } from 'react';

function App() {
  const [component, setComponent] = useState(null);

  const handleNavigation = async (pathname) => {
    const routeComponent = await router.resolve({ pathname });
    setComponent(routeComponent);
  };

  useEffect(() => {
    handleNavigation(window.location.pathname);

    const onPopSt ate = () => handleNavigation(window.location.pathname);
    window.addEventListener('popstate', onPopState);
    
    return () => window.removeEventListener('popstate', onPopState);
  }, []);

  return (
    <div>
      <nav>
        <button onCl ick={() => { window.history.pushState({}, '', '/'); handleNavigation('/'); }}>Home</button>
        <button onCl ick={() => { window.history.pushState({}, '', '/about'); handleNavigation('/about'); }}>About</button>
        <button onCl ick={() => { window.history.pushState({}, '', '/user/42'); handleNavigation('/user/42'); }}>User 42</button>
      </nav>
      <main>{component}</main>
    </div>
  );
}

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

  • router.resolve({ pathname }) возвращает компонент, соответствующий текущему маршруту.
  • popstate событие позволяет отслеживать изменения истории браузера.
  • window.history.pushState обновляет URL без полной перезагрузки страницы.

Использование вложенных маршрутов

Universal Router поддерживает иерархические маршруты. Для React это позволяет создавать компоненты-контейнеры, которые рендерят дочерние компоненты в зависимости от вложенного маршрута.

const routes = [
  {
    path: '/dashboard',
    action: () => <DashboardLayout />,
    children: [
      {
        path: '/stats',
        action: () => <StatsPage />
      },
      {
        path: '/settings',
        action: () => <SettingsPage />
      }
    ]
  }
];

Внутри DashboardLayout можно использовать отдельный вызов router.resolve для рендеринга дочерних компонентов на основе текущего подмаршрута.

Асинхронные действия

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

const routes = [
  {
    path: '/posts/:id',
    action: async ({ params }) => {
      const response = await fetch(`/api/posts/${params.id}`);
      const post = await response.json();
      return <PostPage post={post} />;
    }
  }
];

Преимущества:

  • Данные подгружаются до рендеринга компонента.
  • Универсальный подход для серверного и клиентского рендеринга.

Работа с серверным рендерингом (SSR)

Для SSR Universal Router предоставляет простой способ рендеринга React-компонентов на сервере, возвращая HTML строку.

import { renderToString } from 'react-dom/server';

async function handleRequest(url) {
  const component = await router.resolve({ pathname: url });
  const html = renderToString(component);
  return html;
}

Таким образом, один и тот же набор маршрутов может использоваться и на клиенте, и на сервере, обеспечивая согласованность маршрутизации.

Рекомендации по организации

  • Все маршруты стоит хранить в отдельном файле, чтобы их можно было использовать и на клиенте, и на сервере.
  • Для вложенных маршрутов использовать контейнерные компоненты с отдельным вызовом router.resolve для дочерних компонентов.
  • Асинхронные действия позволяют загружать данные заранее, улучшая UX и поддерживая SSR.
  • Использовать history.pushState и popstate для навигации без перезагрузки страницы.

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