React Router предоставляет готовый инструмент для маршрутизации, но понимание того, как работает роутинг на уровне компонентов, позволяет создавать собственные решения, более гибкие или оптимизированные под конкретные задачи. Кастомная реализация роутинга строится на управлении состоянием текущего пути, прослушивании изменений URL и условном рендеринге компонентов.
Для начала необходимо хранить состояние текущего маршрута. Это можно
сделать с помощью хука useState:
import { useState, useEffect } from 'react';
function useCurrentPath() {
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
const onLocationCha nge = () => setPath(window.location.pathname);
window.addEventListener('popstate', onLocationChange);
return () => window.removeEventListener('popstate', onLocationChange);
}, []);
return path;
}
Ключевой момент: popstate срабатывает при навигации
через кнопки «назад» и «вперед» браузера, что позволяет синхронизировать
состояние с URL.
Основная идея — условный рендеринг компонентов на основе текущего
пути. Создаётся компонент Route:
function Route({ path, element }) {
const currentPath = useCurrentPath();
return currentPath === path ? element : null;
}
Важно: этот простой подход поддерживает только точное совпадение. Для более сложных случаев можно реализовать поддержку параметров и шаблонов.
Для обработки динамических сегментов URL (/user/:id)
применяется разбор строки пути с использованием регулярных
выражений:
function matchPath(routePath, currentPath) {
const paramNames = [];
const regexPath = routePath.replace(/:([^\/]+)/g, (_, key) => {
paramNames.push(key);
return '([^\\/]+)';
});
const match = currentPath.match(new RegExp(`^${regexPath}$`));
if (!match) return null;
const params = paramNames.reduce((acc, name, index) => {
acc[name] = match[index + 1];
return acc;
}, {});
return params;
}
Компонент Route можно расширить для передачи параметров
в рендеримый элемент:
function Route({ path, element }) {
const currentPath = useCurrentPath();
const params = matchPath(path, currentPath);
return params ? React.cloneElement(element, { params }) : null;
}
Для управления переходами создается компонент Link,
который предотвращает полное перезагружение страницы:
function Link({ to, children }) {
const handleClick = (e) => {
e.preventDefault();
window.history.pushState({}, '', to);
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
};
return <a href={to} onCl ick={handleClick}>{children}</a>;
}
Использование window.history.pushState позволяет менять
URL без перезагрузки страницы, а popstate инициирует
обновление состояния маршрута.
Для централизованного управления маршрутами можно создать компонент
Router:
function Router({ children }) {
const currentPath = useCurrentPath();
return React.Children.map(children, child => {
if (!React.isValidElement(child)) return null;
const { path } = child.props;
const params = matchPath(path, currentPath);
return params ? React.cloneElement(child, { params }) : null;
});
}
Такой подход позволяет вкладывать несколько Route внутри
Router и управлять отображением компонентов в зависимости
от пути.
Вложенные маршруты реализуются через передачу дочерних элементов в
Route:
function Route({ path, element, children }) {
const currentPath = useCurrentPath();
const params = matchPath(path, currentPath);
if (!params) return null;
return React.cloneElement(element, { params, children });
}
Маршрут, содержащий дочерние Route, может рендерить их
внутри основного компонента, сохраняя контекст параметров и путь.
Иногда необходимо инициировать навигацию из кода, без клика по ссылке. Для этого создаётся вспомогательная функция:
function navigate(to) {
window.history.pushState({}, '', to);
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
}
Такой подход позволяет программно менять маршрут, например, после успешной отправки формы.
Кастомный роутинг поддерживает ленивую загрузку компонентов с помощью
React.lazy и Suspense:
const UserPage = React.lazy(() => import('./UserPage'));
<Route path="/user/:id" element={
<React.Suspense fallback={<div>Загрузка...</div>}>
<UserPage />
</React.Suspense>
} />
Это улучшает производительность, загружая код компонента только при необходимости.
Для отображения компонента при отсутствии совпадений добавляется специальный маршрут с условием:
function NotFound({ element }) {
const currentPath = useCurrentPath();
const matched = false; // Логика проверки всех маршрутов
return !matched ? element : null;
}
В реальной реализации проверка проходит по всем маршрутам внутри
Router, и если ни один не совпадает, рендерится
NotFound.
window.location.pathname.Route рендерится условно по совпадению пути, с
поддержкой параметров.Link и функция navigate позволяют изменять
URL без перезагрузки.Эта структура формирует фундамент для создания собственного роутинга, полностью управляемого React, без зависимостей от сторонних библиотек.