React Front-end Инженер

React Front-end Инженер

Роадмап навыков для прокачки

Базовое понимание API компонентов: Route, Switch, Link

ReactRoutingReact-router API

Основная идея

React Router — библиотека для клиентской маршрутизации в React-приложениях. Она позволяет отображать разные компоненты в зависимости от URL без перезагрузки страницы. Основные компоненты API: Route определяет соответствие URL и компонента, Switch (в v5) или Routes (в v6) группирует маршруты, Link создаёт навигационные ссылки.

Ключевые аспекты

  • Route — связывает путь URL с компонентом для рендеринга
  • Switch/Routes — рендерит только первый подходящий Route, предотвращая множественное совпадение
  • Link — создаёт ссылку без перезагрузки страницы, в отличие от обычного тега <a>
  • BrowserRouter — обёртка приложения, использующая History API браузера
  • useParams, useNavigate — хуки для доступа к параметрам маршрута и программной навигации

Плюсы

  • Декларативный подход к маршрутизации
  • Полная интеграция с экосистемой React
  • Поддержка вложенных маршрутов
  • Lazy loading компонентов через React.lazy

Минусы

  • Значительные изменения API между версиями (v5 vs v6)
  • Дополнительная зависимость в проекте
  • Требует настройки сервера для корректной работы History API

Частые ошибки на собеседованиях

  • Путают Link и NavLink — NavLink добавляет активный класс для текущего маршрута
  • Забывают, что Route без exact в v5 матчит все пути, начинающиеся с указанного
  • Не понимают разницу между Switch (v5) и Routes (v6)
  • Используют <a href> вместо Link, вызывая полную перезагрузку страницы
  • Не знают про Outlet для вложенных маршрутов в v6

Введение и проблематика

В традиционных веб-приложениях каждый переход между страницами вызывает запрос к серверу и полную перезагрузку. В Single Page Application (SPA) весь код загружается один раз, а навигация происходит на клиенте — меняется только отображаемый контент.

Какую проблему решает React Router?

React сам по себе не предоставляет механизма маршрутизации. React Router заполняет этот пробел, позволяя:

  • Отображать разные компоненты в зависимости от URL
  • Сохранять историю навигации (кнопки «Назад» и «Вперёд» работают)
  • Синхронизировать URL с состоянием приложения
  • Использовать ссылки без перезагрузки страницы

React Router использует History API браузера для управления адресной строкой без отправки запросов на сервер.


Базовая теория

Основные компоненты

КомпонентВерсияОписание
BrowserRouterv5, v6Корневой провайдер, использует History API
HashRouterv5, v6Использует hash (#) в URL для старых браузеров
Routev5, v6Определяет соответствие пути и компонента
Switchv5Рендерит первый совпавший Route
Routesv6Замена Switch в новой версии
Linkv5, v6Ссылка без перезагрузки
NavLinkv5, v6Link с поддержкой активного состояния
Outletv6Место для рендера вложенных маршрутов

Терминология

  • Path — шаблон URL, например /users/:id
  • Match — объект с информацией о совпадении маршрута
  • Params — динамические сегменты пути (:id)
  • Nested Routes — маршруты внутри других маршрутов

Практические примеры

Базовая настройка

Code Example 1: Объясните структуру маршрутизации. Что произойдёт при переходе на /about?

tsx
// App.tsx — React Router v6
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
 
function App() {
  return (
    <BrowserRouter>
      {/* Навигация */}
      <nav>
        <Link to="/">Главная</Link>
        <Link to="/about">О нас</Link>
        <Link to="/users">Пользователи</Link>
      </nav>
 
      {/* Определение маршрутов */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
⚠️

В v5 без exact путь "/" совпадёт со всеми маршрутами. В v6 точное совпадение — поведение по умолчанию.

Компонент Route

Route связывает URL-путь с React-компонентом.

Code Example 2: В чём разница между синтаксисом v5 и v6? Как определяются вложенные маршруты?

tsx
// v6: компонент передаётся через element
<Route path="/users" element={<Users />} />
 
// С параметрами
<Route path="/users/:userId" element={<UserProfile />} />
 
// Вложенные маршруты
<Route path="/dashboard" element={<Dashboard />}>
  <Route path="settings" element={<Settings />} />
  <Route path="profile" element={<Profile />} />
</Route>

Code Example 3: Чем NavLink отличается от Link? Зачем использовать Link вместо обычного <a href>?

tsx
import { Link, NavLink } from 'react-router-dom';
 
function Navigation() {
  return (
    <nav>
      {/* Link — базовая ссылка */}
      <Link to="/home">Главная</Link>
 
      {/* Link с объектом */}
      <Link
        to={{
          pathname: '/search',
          search: '?query=react',
        }}
      >
        Поиск
      </Link>
 
      {/* NavLink — добавляет класс для активного маршрута */}
      <NavLink
        to="/about"
        className={({ isActive }) =>
          isActive ? 'nav-link active' : 'nav-link'
        }
      >
        О нас
      </NavLink>
    </nav>
  );
}

Используйте Link вместо <a href> — это предотвращает полную перезагрузку страницы.

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

Code Example 4: Как получить динамические параметры из URL? Что вернёт useParams для URL /users/123?

tsx
// v6: useParams возвращает типизированный объект
import { useParams } from 'react-router-dom';
 
function UserProfile() {
  const { userId } = useParams<{ userId: string }>();
 
  return <div>Профиль пользователя: {userId}</div>;
}
 
// Маршрут
<Route path="/users/:userId" element={<UserProfile />} />

Программная навигация

Code Example 5: Чем useNavigate (v6) отличается от useHistory (v5)? Как вернуться на предыдущую страницу?

tsx
import { useNavigate } from 'react-router-dom';
 
function LoginForm() {
  const navigate = useNavigate();
 
  const handleSubmit = async (data: FormData) => {
    await login(data);
    navigate('/dashboard'); // переход после логина
  };
 
  const handleCancel = () => {
    navigate(-1); // вернуться назад
  };
 
  return (
    <form onSubmit={handleSubmit}>
      {/* ... */}
      <button type="button" onClick={handleCancel}>
        Отмена
      </button>
    </form>
  );
}

Визуализация

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

graph TD A[BrowserRouter] --> B[Routes/Switch] B --> C["Route: /"] B --> D["Route: /about"] B --> E["Route: /users"] B --> F["Route: /users/:id"] B --> G["Route: *"] C --> H[Home] D --> I[About] E --> J[UsersList] F --> K[UserProfile] G --> L[NotFound]

Процесс матчинга

Пользователь переходит по URL

Пользователь кликает на Link или вводит URL в адресную строку.

Router перехватывает навигацию

BrowserRouter обновляет History API без перезагрузки страницы.

Routes ищет совпадение

Компонент Routes/Switch проходит по всем Route и ищет первое совпадение пути.

Рендер компонента

Найденный Route рендерит свой компонент в element (v6) или component (v5).


Пограничные кейсы

Вложенные маршруты в v6

Code Example 6: Зачем нужен компонент Outlet? Что произойдёт, если его не указать в DashboardLayout?

tsx
// App.tsx
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="settings" element={<Settings />} />
    <Route path="profile" element={<Profile />} />
  </Route>
</Routes>
 
// DashboardLayout.tsx
import { Outlet } from 'react-router-dom';
 
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        {/* Outlet рендерит вложенный маршрут */}
        <Outlet />
      </main>
    </div>
  );
}
⚠️

Не забудьте Outlet в родительском компоненте — без него вложенные маршруты не отобразятся.

Защищённые маршруты

Code Example 7: Как работает защищённый маршрут? Зачем сохраняем location в state?

tsx
import { Navigate, useLocation } from 'react-router-dom';
 
function ProtectedRoute({ children }: { children: JSX.Element }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();
 
  if (!isAuthenticated) {
    // Сохраняем путь для редиректа после логина
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
 
  return children;
}
 
// Использование
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

Lazy loading маршрутов

Code Example 8: Зачем использовать lazy loading для маршрутов? Почему нужен Suspense?

tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
 
// Ленивая загрузка компонентов
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
 
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Плюсы и минусы

АспектПлюсыМинусы
ДекларативностьМаршруты как компонентыСложнее динамическая конфигурация
ИнтеграцияРаботает с React-экосистемойЗависимость от библиотеки
History APIЧистые URL без #Требует настройки сервера
ХукиУдобный доступ к params, navigateТолько внутри Router
ВложенностьГибкие layout-ыНужно понимать Outlet (v6)

Вопросы интервьюера

Q: Чем Link отличается от обычной ссылки <a>?

<a href> вызывает полную перезагрузку страницы с запросом к серверу. Link перехватывает клик, обновляет URL через History API и ререндерит только нужные компоненты без перезагрузки.

Q: Зачем нужен Switch в v5 / Routes в v6?

Без них все совпавшие Route отрендерятся одновременно. Switch/Routes рендерит только первый совпавший маршрут.

Q: Как передать props в компонент Route?

В v6: element={<Component prop="value" />}. В v5: использовать render={() => <Component prop="value" />} вместо component.

Q: Что такое Outlet в v6?

Outlet — это placeholder для рендера вложенного маршрута в родительском layout-компоненте. Аналог {children} для маршрутов.

Q: Как сделать редирект?

В v6: компонент <Navigate to="/path" /> или хук useNavigate(). В v5: <Redirect to="/path" /> или useHistory().push().


Источники

Code Example 1: Basic routing setup v6

❓ Объясните структуру маршрутизации. Что произойдёт при переходе на /about?

tsx
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
 
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Главная</Link>
        <Link to="/about">О нас</Link>
        <Link to="/users">Пользователи</Link>
      </nav>
 
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Code Example 2: Route component v6 vs v5

❓ В чём разница между синтаксисом v5 и v6? Как определяются вложенные маршруты?

v6:

tsx
<Route path="/users" element={<Users />} />
<Route path="/users/:userId" element={<UserProfile />} />
 
<Route path="/dashboard" element={<Dashboard />}>
  <Route path="settings" element={<Settings />} />
  <Route path="profile" element={<Profile />} />
</Route>

v5:

tsx
<Route path="/users" component={Users} />
<Route path="/about" render={() => <About title="О нас" />} />
<Route path="/users/:userId" component={UserProfile} />

Code Example 3: Link and NavLink

❓ Чем NavLink отличается от Link? Зачем использовать Link вместо обычного <a href>?

tsx
import { Link, NavLink } from 'react-router-dom';
 
function Navigation() {
  return (
    <nav>
      <Link to="/home">Главная</Link>
 
      <Link
        to={{
          pathname: '/search',
          search: '?query=react',
        }}
      >
        Поиск
      </Link>
 
      <NavLink
        to="/about"
        className={({ isActive }) =>
          isActive ? 'nav-link active' : 'nav-link'
        }
      >
        О нас
      </NavLink>
    </nav>
  );
}

Code Example 4: useParams hook

❓ Как получить динамические параметры из URL? Что вернёт useParams для URL /users/123?

tsx
import { useParams } from 'react-router-dom';
 
function UserProfile() {
  const { userId } = useParams<{ userId: string }>();
 
  return <div>Профиль пользователя: {userId}</div>;
}
 
// Маршрут
<Route path="/users/:userId" element={<UserProfile />} />

Code Example 5: Programmatic navigation

❓ Чем useNavigate (v6) отличается от useHistory (v5)? Как вернуться на предыдущую страницу?

v6:

tsx
import { useNavigate } from 'react-router-dom';
 
function LoginForm() {
  const navigate = useNavigate();
 
  const handleSubmit = async (data: FormData) => {
    await login(data);
    navigate('/dashboard');
  };
 
  const handleCancel = () => {
    navigate(-1);
  };
}

v5:

tsx
import { useHistory } from 'react-router-dom';
 
function LoginForm() {
  const history = useHistory();
 
  const handleSubmit = async (data: FormData) => {
    await login(data);
    history.push('/dashboard');
  };
 
  const handleCancel = () => {
    history.goBack();
  };
}

Code Example 6: Nested routes with Outlet

❓ Зачем нужен компонент Outlet? Что произойдёт, если его не указать в DashboardLayout?

tsx
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="settings" element={<Settings />} />
    <Route path="profile" element={<Profile />} />
  </Route>
</Routes>
 
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  );
}

Code Example 7: Protected route

❓ Как работает защищённый маршрут? Зачем сохраняем location в state?

tsx
import { Navigate, useLocation } from 'react-router-dom';
 
function ProtectedRoute({ children }: { children: JSX.Element }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();
 
  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
 
  return children;
}
 
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

Code Example 8: Lazy loading routes

❓ Зачем использовать lazy loading для маршрутов? Почему нужен Suspense?

tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
 
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
 
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}