Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11

Типизированная навигация в React Router

Руководство по внедрению типобезопасной навигации в React-приложениях. Как избавиться от сломанных ссылок, получить автокомплит в IDE и перестать бояться рефакторинга роутов. Практические примеры кода, готовые хуки и советы по внедрению в существующие проекты.

806 открытий5К показов
Типизированная навигация в React Router

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

Представьте: пятница, 18:30. Релиз через час. Вы меняете один роут в конфиге — и внезапно половина приложения отлетает. Поздравляем, вы только что познакомились с классической болью фронтендеров.

Проблема кроется в самой природе JavaScript. Строковые пути вроде /users/profile/${id} существуют в коде как обычные строки — без проверок, автокомплита и гарантий корректности. Опечатался в /usres вместо /users — и твоя навигация сломалась, а TypeScript молчит как рыба.

Двадцать лет назад мы кликали по window.location.href, десять лет назад — радовались React Router, а сегодня пора переходить на типизированные решения.

Когда прочитаете эту статью, сможете настроить типобезопасную навигацию в своем проекте, забудете про сломанные ссылки после рефакторинга и перестанете нервничать на релизах.

Суть проблемы

			// Так выглядит обычная навигация в React Router
const navigate = useNavigate();
// Где-то в коде
navigate('/admin/products/123/edit');
// А где-то еще
navigate('/admin/products/' + productId + '/edit');
// И тут тоже
 // Опечатка!
		

Проблема очевидна: путь /admin/products размазан по всему коду. TypeScript не знает, что эти строки связаны с определенной директорией, поэтому не проверяет их корректность. Опечатка в product вместо products — и вылетает ошибка 404.

В больших проектах эта проблема критична. Приложение с 200+ путями, где навигация разбросана по сотне компонентов, превращается в минное поле. Один программист меняет структуру URL, а остальные даже не подозревают об этом. Команды используют TypeScript для типобезопасности, но навигация остается уязвимой.

Что такое типизированная навигация?

Типизированная навигация превращает строковые пути в типизированные объекты. Вместо /admin/products/123/edit программист работает с функциями, которые знают структуру приложения и проверяют корректность написания путей на этапе компиляции.

Представьте GPS-навигатор, который знает все адреса в городе. Вы не можете ввести несуществующую улицу — система сразу выдаст ошибку. Так работает типизированная навигация: TypeScript проверяет, что путь существует, параметры переданы правильно, а структура URL соответствует пути.

			// Вместо строк
navigate('/admin/products/123/edit');
// Типизированная навигация
navigate(routes.admin.products.edit({ id: 123 }));
		

Три ключевых преимущества: автокомплит в IDE, проверка на этапе компиляции и безопасный рефакторинг. Поменяете пути в коде — TypeScript сразу покажет все места, которые нужно обновить.

Польза зависит от уровня разработчика:

  • Джуны получают защиту от опечаток и автокомплит — меньше глупых ошибок и быструю разработку. 
  • Миддлы ускоряют разработку благодаря надежному рефакторингу — можно смело менять структуру URL без страха что-то сломать. 
  • Сеньоры используют типизацию для построения архитектуры приложения — создают переиспользуемые компоненты навигации, проверяют параметры и строят масштабируемые системы путей и директорий.

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

Как использовать React Router вместе с TypeScript

Для базовой типизации в React Router v6 сначала определите структуру путей. Создайте интерфейс, который описывает все пути в приложении:

			// Описываем структуру путей
interface AppRoutes {
  '/': {};
  '/admin': {};
  '/admin/products': {};
  '/admin/products/:id': { id: string };
  '/admin/products/:id/edit': { id: string };
}
// Создаем типизированный объект navigate
const useTypedNavigate = () => {
  const navigate = useNavigate();
  return (
    path: T,
    params?: AppRoutes[T]
  ) => {
    const url = Object.entries(params || {}).reduce(
      (acc, [key, value]) => acc.replace(`:${key}`, value),
      path as string
    );
    navigate(url);
  };
};
		

Типизация параметров URL решает проблему с useParams. Вместо any получаете конкретные типы:

			// Обычный useParams возвращает any
const params = useParams(); // { id?: string | undefined }
// Типизированная версия
const useTypedParams = () => {
  return useParams() as AppRoutes[T];
};
// В компоненте
const ProductEdit = () => {
  const { id } = useTypedParams<'/admin/products/:id/edit'>();
  // TypeScript знает, что id точно существует и это string
};
		

Query-параметры типизируются аналогично через useSearchParams. Создайте интерфейс для каждой страницы с query-параметрами и оберните хук.

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

Как внедрить типизированную навигацию в проекты

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

			// routes.config.ts
export const routeConfig = {
  home: '/',
  admin: {
    root: '/admin',
    products: {
      list: '/admin/products',
      edit: '/admin/products/:id/edit',
      create: '/admin/products/new'
    }
  }
} as const;

// Автоматически генерируем типы
type RouteConfig = typeof routeConfig;
type FlatRoutes = /* сложная магия TypeScript */;
		

Конфигурация избавит от нужды дублировать код при генерации типов. TypeScript автоматически выведет все возможные пути и их параметры из одного объекта.

Современные библиотеки решают проблему из коробки. Например, Tanstack Router предоставляет полностью типизированную систему путей с автогенерацией типов, а Type-route создает типобезопасные пути через API.

			// Tanstack Router пример
const productsRoute = new Route({
  getParentRoute: () => adminRoute,
  path: '/products/$productId/edit',
  component: ProductEditComponent,
});
// Автоматическая типизация параметров
function ProductEditComponent() {
  const { productId } = productsRoute.useParams(); // string
}
		

Библиотека typesafe-routes внедряется даже в крупные проекты без необходимости менять сотни строк кода.

Выбор инструмента зависит от размера программы. Если у вас небольшое приложение — быстрее написать хук в пару строк. Но если разрабатываете сложный сервис — используйте библиотеки.

Как не сломать код при использовании типизированной навигации

Не пытайтесь переписать весь проект за раз — создайте типизированные хуки для новых фич, а старый код обновляйте по мере рефакторинга.

			// Начните с малого
const useTypedNavigate = () => { /* ваш хук */ };

// Используйте фичу в новых компонентах
const NewFeature = () => {
  const navigate = useTypedNavigate();
  // ...
};
		

Чеклист для код-ревью поможет не уронить прод:

  • Все новые navigate() и используют типизированные версии;
  • Параметры путей явно типизированы;
  • Нет магических строк в навигации;
  • Query-параметры описаны интерфейсами.

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

Автоматизация упрощает процесс. ESLint правило no-hardcoded-routes запретит использование строк в навигации. Код-генераторы создают типы из OpenAPI схем или конфигурации роутера.

			// ESLint правило
"rules": {
  "no-hardcoded-routes": "error"
}
		

Производительность не страдает — типы исчезают после компиляции. Теряется немного времени на компиляцию TypeScript, но экономия на отладке перекрывает затраты.

Где применять типизированную навигацию

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

			// Типичная админка
interface AdminRoutes {
  '/dashboard': {};
  '/users': {};
  '/users/:id': { id: string };
  '/orders/:orderId/items/:itemId': { orderId: string; itemId: string };
}
		

E-commerce проекты выигрывают от типизации каталогов и фильтров. Пути вроде /catalog/:category/:subcategory?filters=price,brand содержат много параметров, которые легко сломать при рефакторинге.

Интеграция с Redux и Zustand упрощает синхронизацию состояния с URL. Типизированные селекторы автоматически обновляются при изменении роутов:

			// Redux + типизированные роуты
const selectCurrentUser = (state: RootState, params: { id: string }) => 
  state.users.find(user => user.id === params.id);
// Zustand интеграция
const useUserStore = create((set) => ({
  setCurrentRoute: (route: TypedRoute) => set({ currentRoute: route })
}));
		

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

Типизированная навигация — стандарт современной разработки. Команды, которые до сих пор полагаются на строковые пути, тратят лишнее время на поиск багов. Программисты увереннее рефакторят код, не боятся мелких ошибок и не роняют прод в пятницу вечером из-за одного символа в пути.
Следите за новыми постами
Следите за новыми постами по любимым темам
806 открытий5К показов