React Front-end Инженер

React Front-end Инженер

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

Умение работать с React Router

ReactRoutingReact router

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

React Router — стандартная библиотека маршрутизации для React-приложений. Она позволяет создавать SPA с навигацией между «страницами» без перезагрузки браузера. Умение работать с React Router включает установку, настройку маршрутов, навигацию и получение параметров из URL.

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

  • Установкаnpm install react-router-dom для веб-приложений
  • Настройка — оборачивание приложения в BrowserRouter
  • Определение маршрутов — использование Routes и Route для сопоставления URL с компонентами
  • Навигация — Link для ссылок, useNavigate для программного перехода
  • Параметры — useParams для динамических сегментов, useSearchParams для query-параметров

Плюсы

  • Простой декларативный синтаксис
  • Богатый набор хуков для работы с маршрутизацией
  • Поддержка вложенных маршрутов и layout-ов
  • Активное сообщество и хорошая документация

Минусы

  • Кривая обучения при миграции между мажорными версиями
  • Для SSR нужна дополнительная настройка (или Remix/Next.js)
  • Настройка сервера для корректной работы прямых ссылок

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

  • Не знают разницу между react-router и react-router-dom
  • Забывают обернуть приложение в BrowserRouter — хуки не работают вне Router
  • Путают относительные и абсолютные пути в Link
  • Не понимают, как работает index route для дефолтного вложенного маршрута
  • Используют window.location вместо useNavigate для программной навигации

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

Single Page Application (SPA) загружается один раз, а дальнейшая навигация происходит без обращения к серверу. Но пользователи ожидают привычного поведения: работающих кнопок «Назад»/«Вперёд», возможности поделиться ссылкой на конкретную страницу, закладок.

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

React — это библиотека для построения UI, но она не знает ничего о навигации. React Router добавляет:

  • Сопоставление URL с компонентами
  • Навигацию без перезагрузки
  • Работу с историей браузера
  • Передачу параметров через URL

React Router — де-факто стандарт маршрутизации в React-экосистеме. Альтернативы существуют (Reach Router, Wouter), но React Router используется в большинстве проектов.


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

Пакеты React Router

ПакетНазначение
react-routerЯдро библиотеки (не используется напрямую)
react-router-domДля веб-приложений (браузер)
react-router-nativeДля React Native приложений

Для веб-разработки устанавливается только react-router-dom:

bash
npm install react-router-dom

Ключевые концепции

  1. Router — контекст-провайдер, управляющий состоянием навигации
  2. Route — правило сопоставления пути с компонентом
  3. Link — компонент навигации без перезагрузки
  4. Хуки — useParams, useNavigate, useLocation для работы с маршрутизацией

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

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

Установка пакета

bash
npm install react-router-dom

Оборачивание приложения в Router

В файле main.tsx или index.tsx оберните App в BrowserRouter.

Определение маршрутов

Создайте структуру Routes с компонентами для каждого пути.

Добавление навигации

Используйте Link или NavLink для создания меню.

Структура проекта

    • main.tsx
    • App.tsx
      • Home.tsx
      • About.tsx
      • Users.tsx
      • UserProfile.tsx
      • NotFound.tsx
  • Настройка Router

    Code Example 1: Почему BrowserRouter должен оборачивать всё приложение? Что произойдёт, если компонент использует хуки роутера, но находится вне BrowserRouter?

    tsx
    // main.tsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom';
    import App from './App';
     
    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </React.StrictMode>
    );
    ⚠️

    BrowserRouter должен быть выше всех компонентов, использующих хуки роутера. Обычно его размещают в main.tsx.

    Определение маршрутов

    Code Example 2: Что такое index route? Что означает path="*"? Как работают вложенные маршруты?

    tsx
    // App.tsx
    import { Routes, Route } from 'react-router-dom';
    import Layout from './components/Layout';
    import Home from './pages/Home';
    import About from './pages/About';
    import Users from './pages/Users';
    import UserProfile from './pages/UserProfile';
    import NotFound from './pages/NotFound';
     
    function App() {
      return (
        <Routes>
          {/* Маршрут с layout */}
          <Route path="/" element={<Layout />}>
            {/* index — дефолтный вложенный маршрут */}
            <Route index element={<Home />} />
            <Route path="about" element={<About />} />
            <Route path="users" element={<Users />} />
            {/* Динамический параметр :userId */}
            <Route path="users/:userId" element={<UserProfile />} />
            {/* Catch-all для 404 */}
            <Route path="*" element={<NotFound />} />
          </Route>
        </Routes>
      );
    }
     
    export default App;

    Layout с навигацией и Outlet

    Code Example 3: Зачем нужен Outlet? Что будет отображаться внутри <main> при переходе на /about?

    tsx
    // components/Layout.tsx
    import { Outlet, Link, NavLink } from 'react-router-dom';
     
    function Layout() {
      return (
        <div className="app">
          <header>
            <nav>
              {/* NavLink добавляет класс для активного маршрута */}
              <NavLink
                to="/"
                className={({ isActive }) => isActive ? 'active' : ''}
              >
                Главная
              </NavLink>
              <NavLink
                to="/about"
                className={({ isActive }) => isActive ? 'active' : ''}
              >
                О нас
              </NavLink>
              <NavLink
                to="/users"
                className={({ isActive }) => isActive ? 'active' : ''}
              >
                Пользователи
              </NavLink>
            </nav>
          </header>
     
          <main>
            {/* Outlet рендерит вложенный маршрут */}
            <Outlet />
          </main>
     
          <footer>
            © 2024 My App
          </footer>
        </div>
      );
    }
     
    export default Layout;

    Работа с параметрами URL

    Code Example 4: Как получить параметр из URL? В чём разница между navigate('/users') и navigate(-1)?

    tsx
    // pages/UserProfile.tsx
    import { useParams, useNavigate } from 'react-router-dom';
     
    function UserProfile() {
      // Получаем параметр из URL
      const { userId } = useParams<{ userId: string }>();
      const navigate = useNavigate();
     
      const handleBack = () => {
        navigate('/users'); // программный переход
        // или navigate(-1) для перехода назад
      };
     
      return (
        <div>
          <button onClick={handleBack}>← Назад к списку</button>
          <h1>Профиль пользователя #{userId}</h1>
          {/* Загрузка данных пользователя по userId */}
        </div>
      );
    }
     
    export default UserProfile;

    Список с навигацией к деталям

    Code Example 5: В чём разница между относительным и абсолютным путём в Link? Что получится для каждого варианта?

    tsx
    // pages/Users.tsx
    import { Link } from 'react-router-dom';
     
    const users = [
      { id: '1', name: 'Алексей' },
      { id: '2', name: 'Мария' },
      { id: '3', name: 'Иван' },
    ];
     
    function Users() {
      return (
        <div>
          <h1>Пользователи</h1>
          <ul>
            {users.map(user => (
              <li key={user.id}>
                {/* Относительный путь — добавится к текущему /users */}
                <Link to={user.id}>{user.name}</Link>
                {/* Или абсолютный путь */}
                {/* <Link to={`/users/${user.id}`}>{user.name}</Link> */}
              </li>
            ))}
          </ul>
        </div>
      );
    }
     
    export default Users;

    Хуки React Router

    Основные хуки

    Code Example 6: Объясните назначение каждого хука. Как получить query-параметр из URL?

    tsx
    import { useParams } from 'react-router-dom';
     
    // URL: /posts/123
    function Post() {
      const { postId } = useParams<{ postId: string }>();
      // postId === '123'
     
      return <div>Пост #{postId}</div>;
    }

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

    Относительные vs абсолютные пути

    tsx
    // Текущий URL: /users/123/posts
     
    // Относительный путь — от текущего маршрута
    <Link to="new">Новый пост</Link>
    // Результат: /users/123/posts/new
     
    // Абсолютный путь — от корня
    <Link to="/posts/new">Новый пост</Link>
    // Результат: /posts/new
     
    // Переход на уровень выше
    <Link to="..">Назад</Link>
    // Результат: /users/123
    ⚠️

    Относительные пути удобны для вложенных маршрутов, но могут запутать. Если сомневаетесь — используйте абсолютные пути.

    Передача state при навигации

    Code Example 7: Как передать данные между страницами без URL-параметров? Как получить переданные данные?

    tsx
    // Отправитель
    const navigate = useNavigate();
    navigate('/checkout', {
      state: { from: 'cart', items: cartItems }
    });
     
    // Или через Link
    <Link to="/checkout" state={{ from: 'cart', items: cartItems }}>
      Оформить
    </Link>
     
    // Получатель
    const location = useLocation();
    const { from, items } = location.state || {};

    404 страница

    tsx
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      {/* Любой не совпавший маршрут */}
      <Route path="*" element={<NotFound />} />
    </Routes>

    Редирект

    Code Example 8: Чем Navigate отличается от useNavigate? Зачем нужен replace?

    tsx
    import { Navigate } from 'react-router-dom';
     
    // Редирект в JSX
    <Route path="/old-path" element={<Navigate to="/new-path" replace />} />
     
    // Условный редирект
    function ProtectedPage() {
      const { isLoggedIn } = useAuth();
     
      if (!isLoggedIn) {
        return <Navigate to="/login" replace />;
      }
     
      return <Dashboard />;
    }

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

    АспектПлюсыМинусы
    СинтаксисДекларативный, React-подобныйИзменения между версиями
    ХукиУдобный доступ к данным маршрутаРаботают только внутри Router
    ВложенностьГибкие layout-ыТребует понимания Outlet
    ИсторияПолная поддержка History APIНастройка сервера для SPA
    TypeScriptХорошая типизацияПараметры всегда string

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

    Q: Чем отличается react-router от react-router-dom?

    react-router — ядро с базовой логикой. react-router-dom — пакет для веб с компонентами BrowserRouter, Link и DOM-специфичными хуками. В проекте устанавливается только react-router-dom.

    Q: Когда использовать useNavigate вместо Link?

    Link — для декларативной навигации по клику. useNavigate — для программной навигации после действия (отправка формы, таймаут, успешный запрос).

    Q: Что такое index route?

    Index route рендерится, когда путь родителя совпал точно, без дополнительного сегмента. Например, /dashboard показывает index, а /dashboard/settings — вложенный маршрут settings.

    Q: Как работает BrowserRouter под капотом?

    Использует History API (pushState, replaceState, popstate) для изменения URL без перезагрузки. При изменении URL React Router обновляет контекст, и компоненты перерендериваются.

    Q: Как сделать lazy loading для маршрутов?

    Использовать React.lazy() для компонентов и обернуть Routes в Suspense с fallback-компонентом загрузки.


    Источники

    Code Example 1: BrowserRouter setup

    ❓ Почему BrowserRouter должен оборачивать всё приложение? Что произойдёт, если компонент использует хуки роутера, но находится вне BrowserRouter?

    tsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom';
    import App from './App';
     
    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </React.StrictMode>
    );

    Code Example 2: Routes definition

    ❓ Что такое index route? Что означает path="*"? Как работают вложенные маршруты?

    tsx
    import { Routes, Route } from 'react-router-dom';
     
    function App() {
      return (
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route index element={<Home />} />
            <Route path="about" element={<About />} />
            <Route path="users" element={<Users />} />
            <Route path="users/:userId" element={<UserProfile />} />
            <Route path="*" element={<NotFound />} />
          </Route>
        </Routes>
      );
    }

    Code Example 3: Layout with Outlet

    ❓ Зачем нужен Outlet? Что будет отображаться внутри <main> при переходе на /about?

    tsx
    import { Outlet, NavLink } from 'react-router-dom';
     
    function Layout() {
      return (
        <div className="app">
          <header>
            <nav>
              <NavLink
                to="/"
                className={({ isActive }) => isActive ? 'active' : ''}
              >
                Главная
              </NavLink>
              <NavLink
                to="/about"
                className={({ isActive }) => isActive ? 'active' : ''}
              >
                О нас
              </NavLink>
            </nav>
          </header>
     
          <main>
            <Outlet />
          </main>
        </div>
      );
    }

    Code Example 4: useParams and useNavigate

    ❓ Как получить параметр из URL? В чём разница между navigate('/users') и navigate(-1)?

    tsx
    import { useParams, useNavigate } from 'react-router-dom';
     
    function UserProfile() {
      const { userId } = useParams<{ userId: string }>();
      const navigate = useNavigate();
     
      const handleBack = () => {
        navigate('/users');
      };
     
      return (
        <div>
          <button onClick={handleBack}>← Назад к списку</button>
          <h1>Профиль пользователя #{userId}</h1>
        </div>
      );
    }

    Code Example 5: Link navigation

    ❓ В чём разница между относительным и абсолютным путём в Link? Что получится для каждого варианта?

    tsx
    import { Link } from 'react-router-dom';
     
    const users = [
      { id: '1', name: 'Алексей' },
      { id: '2', name: 'Мария' },
    ];
     
    function Users() {
      return (
        <ul>
          {users.map(user => (
            <li key={user.id}>
              <Link to={user.id}>{user.name}</Link>
              {/* <Link to={`/users/${user.id}`}>{user.name}</Link> */}
            </li>
          ))}
        </ul>
      );
    }

    Code Example 6: Router hooks

    ❓ Объясните назначение каждого хука. Как получить query-параметр из URL?

    tsx
    import { useParams, useNavigate, useLocation, useSearchParams } from 'react-router-dom';
     
    // URL: /search?query=react&page=2
    function SearchPage() {
      const [searchParams, setSearchParams] = useSearchParams();
     
      const query = searchParams.get('query');
      const page = searchParams.get('page');
     
      const handleNextPage = () => {
        setSearchParams({
          query: query || '',
          page: String(Number(page) + 1),
        });
      };
     
      return (
        <div>
          <p>Поиск: {query}, страница: {page}</p>
          <button onClick={handleNextPage}>Следующая</button>
        </div>
      );
    }

    Code Example 7: Passing state on navigation

    ❓ Как передать данные между страницами без URL-параметров? Как получить переданные данные?

    tsx
    const navigate = useNavigate();
    navigate('/checkout', {
      state: { from: 'cart', items: cartItems }
    });
     
    <Link to="/checkout" state={{ from: 'cart', items: cartItems }}>
      Оформить
    </Link>
     
    const location = useLocation();
    const { from, items } = location.state || {};

    Code Example 8: Navigate component for redirect

    ❓ Чем Navigate отличается от useNavigate? Зачем нужен replace?

    tsx
    import { Navigate } from 'react-router-dom';
     
    <Route path="/old-path" element={<Navigate to="/new-path" replace />} />
     
    function ProtectedPage() {
      const { isLoggedIn } = useAuth();
     
      if (!isLoggedIn) {
        return <Navigate to="/login" replace />;
      }
     
      return <Dashboard />;
    }