Конфигурационная маршрутизация

Конфигурационная маршрутизация в React Router предоставляет возможность описывать все маршруты приложения централизованно в виде структуры данных, а не полагаться на вложенные JSX-компоненты <Route>. Такой подход облегчает управление сложными приложениями с большим числом маршрутов, позволяет динамически генерировать маршруты и упрощает интеграцию с системами авторизации и ленивой загрузки.


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

В React Router маршруты конфигурируются через массив объектов, где каждый объект описывает путь (path), компонент (element) и дополнительные параметры, такие как children для вложенных маршрутов или loader для предварительной загрузки данных. Пример базовой структуры:

const routes = [
  {
    path: '/',
    element: <HomePage />,
  },
  {
    path: 'about',
    element: <AboutPage />,
  },
  {
    path: 'dashboard',
    element: <DashboardLayout />,
    children: [
      { path: 'stats', element: <StatsPage /> },
      { path: 'settings', element: <SettingsPage /> },
    ],
  },
];

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

  • path указывает URL-адрес маршрута.
  • element задаёт компонент, который будет отрисован.
  • children содержит вложенные маршруты, формируя иерархию.

Создание маршрутизатора

После определения конфигурации маршрутов необходимо создать маршрутизатор с помощью функции createBrowserRouter или createHashRouter в зависимости от используемого типа истории.

import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter(routes);

function App() {
  return <RouterProvider router={router} />;
}

Особенности RouterProvider:

  • Принимает объект маршрутизатора.
  • Отвечает за обработку переходов и синхронизацию состояния URL.
  • Обеспечивает поддержку вложенных маршрутов и ленивой загрузки компонентов.

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

Вложенные маршруты позволяют использовать общие макеты и навигационные элементы. Компонент верхнего уровня (element) может включать <Outlet />, который будет рендерить дочерние маршруты:

function DashboardLayout() {
  return (
    <div>
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  );
}
const routes = [
  {
    path: 'dashboard',
    element: <DashboardLayout />,
    children: [
      { path: 'stats', element: <StatsPage /> },
      { path: 'settings', element: <SettingsPage /> },
    ],
  },
];

Использование <Outlet /> делает маршруты модульными и облегчает повторное использование макетов.


Ленивый импорт компонентов

Для оптимизации загрузки больших приложений React Router поддерживает ленивый импорт через React.lazy и Suspense. В конфигурации маршрутов это выглядит следующим образом:

import { lazy, Suspense } from 'react';

const StatsPage = lazy(() => import('./pages/StatsPage'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));

const routes = [
  {
    path: 'dashboard',
    element: <DashboardLayout />,
    children: [
      {
        path: 'stats',
        element: (
          <Suspense fallback={<div>Загрузка...</div>}>
            <StatsPage />
          </Suspense>
        ),
      },
      {
        path: 'settings',
        element: (
          <Suspense fallback={<div>Загрузка...</div>}>
            <SettingsPage />
          </Suspense>
        ),
      },
    ],
  },
];

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


Предзагрузка данных с помощью loader

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

import { useLoaderData } from 'react-router-dom';

function StatsPage() {
  const data = useLoaderData();
  return <div>Статистика: {JSON.stringify(data)}</div>;
}

const routes = [
  {
    path: 'dashboard/stats',
    element: <StatsPage />,
    loader: async () => {
      const response = await fetch('/api/stats');
      return response.json();
    },
  },
];

Преимущества использования loader:

  • Упрощает управление состоянием загрузки данных.
  • Избавляет от необходимости вызывать useEffect для каждой страницы.
  • Позволяет строить маршруты с асинхронной логикой чисто через конфигурацию.

Защищённые маршруты и авторизация

Конфигурационная маршрутизация упрощает реализацию контроля доступа через обертки (wrapper) или условный рендеринг:

function ProtectedRoute({ element }) {
  const isAuthenticated = /* логика проверки авторизации */;
  return isAuthenticated ? element : <Navigate to="/login" />;
}

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

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


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

Конфигурация поддерживает указание компонента для отображения ошибок (errorElement) на уровне маршрута:

const routes = [
  {
    path: 'dashboard',
    element: <DashboardLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: 'stats', element: <StatsPage /> },
      { path: 'settings', element: <SettingsPage /> },
    ],
  },
];

Особенности:

  • errorElement рендерится при сбое loader или ошибки компонента.
  • Позволяет создавать индивидуальные страницы ошибок для разных секций приложения.

Динамические маршруты и параметры URL

Маршруты могут содержать динамические сегменты с использованием двоеточия (:):

const routes = [
  {
    path: 'users/:userId',
    element: <UserPage />,
  },
];

Доступ к параметрам осуществляется через хук useParams:

import { useParams } from 'react-router-dom';

function UserPage() {
  const { userId } = useParams();
  return <div>Профиль пользователя: {userId}</div>;
}

Примечания:

  • Динамические сегменты упрощают работу с REST-подобными маршрутами.
  • Можно комбинировать с loader для предзагрузки данных конкретного пользователя.

Итоговая структура для большого приложения

Для крупных проектов рекомендуется использовать следующую организацию:

  • Центральный файл конфигурации маршрутов (routes.js) с массивом объектов маршрутов.
  • Лейауты с <Outlet /> для повторно используемых компонентов интерфейса.
  • Ленивый импорт страниц для оптимизации бандла.
  • loader для асинхронной загрузки данных.
  • Защищённые маршруты через обертки.
  • Компоненты ошибок через errorElement.

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