React Router предоставляет мощный механизм для работы с маршрутизацией в приложениях на React. С появлением TypeScript появилась возможность усиливать маршруты строгой типизацией, что позволяет значительно уменьшить количество ошибок во время разработки и повышает безопасность навигации. Генерация типов из маршрутов становится особенно важной в больших приложениях с многочисленными маршрутами и вложенными страницами.
В React Router маршруты обычно описываются с помощью компонента
<Routes> и вложенных <Route>.
Пример базовой структуры:
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Profile from './pages/Profile';
const AppRoutes = () => (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profile/:userId" element={<Profile />} />
</Routes>
);
Здесь path задаёт URL, а element —
компонент, который рендерится при совпадении пути. В случае динамических
сегментов, таких как :userId, важно корректно типизировать
параметры маршрута.
TypeScript позволяет извлекать параметры маршрутов и строго
типизировать их, используя хук useParams. Пример:
import { useParams } from 'react-router-dom';
interface ProfileParams {
userId: string;
}
const Profile = () => {
const params = useParams<ProfileParams>();
return <div>Профиль пользователя {params.userId}</div>;
};
Ключевым моментом здесь является интерфейс
ProfileParams, который описывает все динамические сегменты
URL. Это исключает возможность опечаток при обращении к параметрам
маршрута.
В больших приложениях маршруты часто описываются в виде конфигурационного массива. Это позволяет централизовать маршруты и использовать их повторно. Пример:
const routesConfig = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/profile/:userId', component: Profile },
] as const;
Использование as const превращает массив в литеральный
тип, что позволяет TypeScript точно определить возможные пути. Далее
можно автоматически генерировать типы для path и параметров
маршрута.
type RoutePath = typeof routesConfig[number]['path'];
Теперь RoutePath будет строго соответствовать всем
указанным маршрутам:
'/' | '/about' | '/profile/:userId'.
Для динамических маршрутов важно автоматически извлекать параметры. Это можно сделать с помощью условных типов и шаблонных литералов TypeScript:
type ExtractRouteParams<T extends string> =
T extends `${infer _Start}/:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: T extends `${infer _Start}/:${infer Param}`
? Param
: never;
type ProfileRouteParams = ExtractRouteParams<'/profile/:userId'>; // "userId"
Эта конструкция рекурсивно обрабатывает строку маршрута и извлекает все динамические сегменты, превращая их в объединение строковых литералов. Для маршрута с несколькими параметрами тип будет автоматически включать все ключи.
Генерация типов позволяет безопасно строить функции навигации. Вместо передачи строки вручную используется строго типизированная функция:
import { useNavigate } from 'react-router-dom';
const useTypedNavigate = () => {
const navigate = useNavigate();
return <T extends RoutePath>(path: T, params?: Record<ExtractRouteParams<T>, string>) => {
let resolvedPath = path;
if (params) {
for (const key in params) {
resolvedPath = resolvedPath.replace(`:${key}`, params[key]);
}
}
navigate(resolvedPath);
};
};
// Пример использования:
const goToProfile = useTypedNavigate();
goToProfile('/profile/:userId', { userId: '123' });
Такой подход обеспечивает компиляторную проверку корректности маршрутов и параметров. Любая ошибка, например опечатка в параметре, будет выявлена на этапе компиляции.
Вложенные маршруты часто встречаются в больших приложениях. Например:
const routesConfig = [
{
path: '/dashboard',
component: Dashboard,
children: [
{ path: 'analytics', component: Analytics },
{ path: 'reports', component: Reports },
],
},
] as const;
Для их типизации можно использовать рекурсивные условные типы:
type NestedRoutePath<T> = T extends { path: infer P; children: infer C }
? P | NestedRoutePath<C[number]>
: never;
type AppRoutePaths = NestedRoutePath<typeof routesConfig>;
// "/dashboard" | "analytics" | "reports"
Это позволяет автоматически получать все пути, включая вложенные, и интегрировать их с системой строгой навигации.
Использование TypeScript для генерации типов из маршрутов в React Router повышает устойчивость приложения к ошибкам, особенно в сложных системах с динамическими и вложенными маршрутами. Такой подход формирует основу для строгой и удобной навигации во всей архитектуре приложения.