C
React/ルーティング/Lesson 14

React Router

30分·theory
JavaScript

React Router

💡 なぜ学ぶべきか?

🎯 ページのリロードなしに高速で画面を切り替えるSPAを構築できます。
💼 ブラウザの戻るボタンやブックマークなど、基本的な機能が自動的にサポートされます。
大規模プロジェクトで最も多く使われる標準的なルーティングライブラリです。
🏢 실무에서는
Toss・Karrot Market・Baeminなどの企業がReact Routerをコア技術として採用しています。採用要件として「React Router経験」は非常に一般的であり、知らないと実務プロジェクトへの参加が難しくなります。

概念

React Router v6は現代のSPAナビゲーションの標準であり、ネストされたルーティングとOutletによるレイアウト管理、そしてloaderによるデータフェッチの最適化がその核心です。大規模プロジェクトに不可欠なルーティングアーキテクチャスキルです。

なぜ重要なのか

実務でadminページやダッシュボードを構築する際、サイドバー・ヘッダー・コンテンツ構造のネストされたレイアウトは必須です。loaderパターンにより、ページ遷移前にデータを事前ロードしてUXを大幅に改善できます。特にNext.jsではなくReact SPAを採用したプロジェクトでは必ず押さえておくべき技術です。

コアコンセプト

React Router v6はマンションの構造に似ています。ネストされたルーティングは「棟・号室・部屋」のような階層構造であり、Outletは各フロアで子コンポーネントが入る「空きスペース」であり、loaderは部屋に入る前に必要なものをあらかじめ準備しておくことに相当します。

ポイント

  • ネストされたルーティングでURLの階層構造とコンポーネント構造を一致させる
  • OutletコンポーネントによるParent-Childレイアウト管理
  • loader関数でコンポーネントのレンダリング前にデータを事前ロード
💻 悪い例 — v5スタイルのネストなしルーティング
// App.tsx - アンチパターン
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() { 
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/dashboard/users" element={<DashboardUsers />} />
        <Route path="/dashboard/products" element={<DashboardProducts />} />
        <Route path="/dashboard/settings" element={<DashboardSettings />} />
      </Routes>
    </BrowserRouter>
  );
}

// 各Dashboardコンポーネントごとに重複したレイアウトコード
function DashboardUsers() { 
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <Header />
      <div className="content">
        <h1>ユーザー管理</h1>
        {/* ユーザーリスト */}
      </div>
    </div>
  );
}
💻 良い例 — v6のネストされたルーティングとOutletの活用
// App.tsx - 2025 推奨パターン
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import { DashboardLayout } from './components/DashboardLayout';
import { Users, Products, Settings } from './pages';

// ネストされたルーティングの定義
const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/dashboard',
    element: <DashboardLayout />, // 親レイアウト
    children: [
      {
        index: true, // /dashboard デフォルトパス
        element: <DashboardHome />,
      },
      {
        path: 'users',
        element: <Users />,
        loader: usersLoader, // データを事前にロード
      },
      {
        path: 'products',
        element: <Products />,
        loader: productsLoader,
      },
      {
        path: 'settings',
        element: <Settings />,
      },
    ],
  },
]);

function App() { 
  return <RouterProvider router={router} />;
}

// DashboardLayout.tsx - 再利用可能なレイアウト
function DashboardLayout() { 
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <div className="main-content">
        <Header />
        <div className="content">
          <Outlet /> {/* 子コンポーネントがここにレンダリングされます */}
        </div>
      </div>
    </div>
  );
}

// loader関数群 - コンポーネントレンダリング前のデータロード
export async function usersLoader() { 
  const users = await fetch('/api/users');
  return users.json();
}

// Users.tsx - loaderデータの使用
import { useLoaderData } from 'react-router-dom';

function Users() { 
  const users = useLoaderData() as User[];
  
  return (
    <div>
      <h1>ユーザー管理</h1>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}
💻 応用例 — エラーバウンダリとローディング状態のハンドリング
// router.tsx - エラー処理とローディング状態まで考慮した完全版
import { createBrowserRouter, isRouteErrorResponse } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/dashboard',
    element: <DashboardLayout />,
    errorElement: <DashboardErrorBoundary />, // エラーバウンダリ
    children: [
      {
        path: 'users',
        element: <Users />,
        loader: usersLoader,
        errorElement: <UsersError />, // ページごとのエラー処理
      },
    ],
  },
]);

// エラーバウンダリコンポーネント
function DashboardErrorBoundary() { 
  const error = useRouteError();
  
  if (isRouteErrorResponse(error)) {
    return (
      <div className="error-page">
        <h1>{error.status} {error.statusText}</h1>
        <p>{error.data}</p>
      </div>
    );
  }
  
  return (
    <div className="error-page">
      <h1>予期しないエラーが発生しました</h1>
    </div>
  );
}

// loaderでのエラー処理
export async function usersLoader() { 
  try {
    const response = await fetch('/api/users');
    if (!response.ok) {
      throw new Response('ユーザーデータを読み込めませんでした', {
        status: response.status,
        statusText: response.statusText,
      });
    }
    return response.json();
  } catch (error) { 
    throw new Response('ネットワークエラー', { status: 500 });
  }
}

// ローディング状態表示のためのSuspense活用
function App() { 
  return (
    <Suspense fallback={<GlobalLoadingSpinner />}>
      <RouterProvider router={router} />
    </Suspense>
  );
}

💡 ⚠️ よくあるミス

  • Outletを忘れてネストされたルートがレンダリングされない場合 — 親コンポーネントに必ずを追加する必要がある
  • loaderでエラー処理をしていないためアプリがクラッシュする場合 — try-catchでエラーをResponseオブジェクトとしてthrowする必要がある
  • index: trueとpath: ''を混同している場合 — indexは親パスのデフォルト、path: ''はすべての子パスにマッチ

💡 🎯 面接対策

Q: React Router v6のネストされたルーティングとv5の違いを説明してください
Q: Outletコンポーネントの役割と使い方を説明してください
Q: loaderパターンを使う理由とメリットを教えてください

ヒント: ネストされたルーティングではURL構造とコンポーネント階層の一致・レイアウト再利用性の向上を最初に挙げ、OutletはChildコンポーネントのレンダリング位置の指定、loaderはコンポーネントレンダリング前のデータ事前ロードによるUX改善というキーワードで回答を構成してください。

⚛️ Reactパターン — React Router

React RouterをReactでどのように使うか、コードとともにステップバイステップで学びましょう。
1 🛣️ 1. Routerのインストール
$ npm install react-router-dom
2 📋 2. ルートの定義
URL → コンポーネントのマッピング
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
</Routes>
3 🔗 3. リンクで移動
<Link>でページ全体のリロードなしに移動
<Link to="/about">紹介</Link>
4 🎯 4. 動的パラメータ
/users/:idのような動的ルーティング + useParams

🎮 React Router — ステップバイステップで理解

各ステップをクリックして内容を読み、✓ 理解しましたボタンで進捗を確認してください。
🖥️ 実行結果 — レンダリングされたReactコンポーネント
✏️ React 코드 수정하기 (클릭해서 열기)
⚛️ React 18 + Babel Standalone — まず結果を確認し、エディタで自由に編集できます。

確認クイズ

React Routerで宣言的にページ遷移するために使うコンポーネントは何ですか?
💡 <Link to="/path"> はアンカータグとしてレンダリングされますが、ページのリロードなしにSPAナビゲーションを行います。プログラムによる遷移にはuseNavigate()を使用します。
先に読むとよい概念: useContext
次のおすすめ: データフェッチング
React Router - React