Reactのルーティング入門(React Router v6)

はじめに

モダンなウェブアプリケーションでは、ユーザーが異なるページ間を移動できることが重要です。Reactはシングルページアプリケーション(SPA)を構築するためのライブラリですが、単体ではページ間のナビゲーション機能を持っていません。そこで登場するのが「React Router」です。

この記事では、React Router v6の基本的な使い方から応用テクニックまで、実用的なサンプルコードとともに解説します。

React Routerとは?

React Routerは、Reactアプリケーションでルーティングを実現するための標準的なライブラリです。URLに基づいて異なるコンポーネントを表示することで、複数ページを持つアプリケーションのような体験を提供します。

React Router v6は2021年末にリリースされ、以前のバージョンから大幅に改良されました。シンプルなAPI、Hooks APIの強化、自動的なルートランキングなど、多くの新機能が追加されています。

インストール方法

プロジェクトにReact Router v6を追加するには、npm(またはyarn)を使用します:

# npmの場合
npm install react-router-dom

# yarnの場合
yarn add react-router-dom

基本的な使い方

1. アプリケーションをルーターでラップする

まず、アプリケーション全体をBrowserRouterコンポーネントでラップします:

// index.js または main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

2. ルートの定義

次に、RoutesRouteコンポーネントを使用して、URLパスとコンポーネントをマッピングします:

// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

3. ナビゲーションリンクの作成

Linkコンポーネントを使用して、ページ間のナビゲーションリンクを作成します:

// Navigation.jsx
import { Link } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <ul>
        <li><Link to="/">ホーム</Link></li>
        <li><Link to="/about">会社概要</Link></li>
        <li><Link to="/contact">お問い合わせ</Link></li>
      </ul>
    </nav>
  );
}

export default Navigation;

ネストされたルート

React Router v6では、ルートをネストさせることが非常に簡単になりました。

// App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import Blog from './pages/Blog';
import BlogPost from './pages/BlogPost';
import NotFound from './pages/NotFound';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="contact" element={<Contact />} />
        <Route path="blog" element={<Blog />} />
        <Route path="blog/:postId" element={<BlogPost />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  );
}

export default App;

親レイアウトコンポーネントでは、Outletコンポーネントを使用して子ルートのコンテンツを表示する場所を指定します:

// Layout.jsx
import { Outlet } from 'react-router-dom';
import Navigation from './Navigation';
import Footer from './Footer';

function Layout() {
  return (
    <>
      <header>
        <Navigation />
      </header>
      <main>
        <Outlet /> {/* ここに子ルートのコンポーネントが表示される */}
      </main>
      <Footer />
    </>
  );
}

export default Layout;

URLパラメータの取得

ブログ記事やユーザープロフィールなど、動的なコンテンツを表示する場合、URLパラメータを使用します。React Router v6では、useParamsフックを使ってパラメータを簡単に取得できます:

// BlogPost.jsx
import { useParams } from 'react-router-dom';

function BlogPost() {
  const { postId } = useParams();
  
  return (
    <div>
      <h1>ブログ記事 {postId}</h1>
      {/* ここで postId を使ってデータを取得・表示 */}
    </div>
  );
}

export default BlogPost;

プログラムによるナビゲーション

フォーム送信後やボタンクリック時など、特定のイベントでページ遷移を行いたい場合があります。React Router v6では、useNavigateフックを使用します:

// LoginForm.jsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const navigate = useNavigate();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    
    // ログイン処理(仮)
    if (username === 'admin' && password === 'password') {
      // ログイン成功時、ダッシュボードページへ遷移
      navigate('/dashboard');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>ユーザー名:</label>
        <input
          type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
      </div>
      <div>
        <label>パスワード:</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
      </div>
      <button type="submit">ログイン</button>
    </form>
  );
}

export default LoginForm;

クエリパラメータの処理

検索フィルターやページネーションなど、URLのクエリパラメータを扱う場合は、useSearchParamsフックを使用します:

// ProductList.jsx
import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category') || 'all';
  const page = parseInt(searchParams.get('page') || '1');
  
  const handleCategoryChange = (newCategory) => {
    setSearchParams({ category: newCategory, page: '1' });
  };
  
  const nextPage = () => {
    setSearchParams({ category, page: (page + 1).toString() });
  };
  
  return (
    <div>
      <h1>商品一覧</h1>
      
      <div>
        <button onClick={() => handleCategoryChange('all')}>全て</button>
        <button onClick={() => handleCategoryChange('electronics')}>家電</button>
        <button onClick={() => handleCategoryChange('books')}>書籍</button>
      </div>
      
      <div>
        <p>現在のカテゴリ: {category}</p>
        <p>ページ: {page}</p>
        {/* 商品リストを表示 */}
      </div>
      
      <button onClick={nextPage}>次のページ</button>
    </div>
  );
}

export default ProductList;

ナビゲーションガード(保護されたルート)

ログイン状態に応じてアクセスを制限したい場合は、独自のコンポーネントを作成します:

// RequireAuth.jsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext'; // 認証コンテキスト(例)

function RequireAuth({ children }) {
  const { user } = useAuth();
  const location = useLocation();
  
  if (!user) {
    // ログインしていない場合、ログインページにリダイレクト
    // アクセスしようとしていたパスを state に保存して、ログイン後に戻れるようにする
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

export default RequireAuth;

この保護コンポーネントをルート定義で使用します:

// App.jsx
// ...他のインポート
import RequireAuth from './components/RequireAuth';
import Dashboard from './pages/Dashboard';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        {/* 公開ルート */}
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="login" element={<Login />} />
        
        {/* 保護されたルート */}
        <Route
          path="dashboard"
          element={
            <RequireAuth>
              <Dashboard />
            </RequireAuth>
          }
        />
        
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  );
}

export default App;

アクティブリンクのスタイリング

現在のページに対応するナビゲーションリンクを強調表示したい場合は、NavLinkコンポーネントを使用します:

// Navigation.jsx
import { NavLink } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <ul>
        <li>
          <NavLink
            to="/"
            className={({ isActive }) => isActive ? 'active-link' : ''}
            end
          >
            ホーム
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/about"
            className={({ isActive }) => isActive ? 'active-link' : ''}
          >
            会社概要
          </NavLink>
        </li>
        <li>
          <NavLink
            to="/contact"
            className={({ isActive }) => isActive ? 'active-link' : ''}
          >
            お問い合わせ
          </NavLink>
        </li>
      </ul>
    </nav>
  );
}

export default Navigation;

CSSで以下のようにスタイルを定義します:

.active-link {
  font-weight: bold;
  color: #0066cc;
  text-decoration: underline;
}

レイジーローディング(遅延読み込み)

アプリケーションが大きくなってくると、初期ロード時間を短縮するためにコード分割が必要になります。React.lazyとSuspenseを使用して、必要なコンポーネントだけを動的にインポートできます:

// App.jsx
import React, { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Loading from './components/Loading';

// レイジーロード
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const NotFound = lazy(() => import('./pages/NotFound'));

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        
        {/* レイジーロードされるルート */}
        <Route path="about" element={
          <Suspense fallback={<Loading />}>
            <About />
          </Suspense>
        } />
        
        <Route path="contact" element={
          <Suspense fallback={<Loading />}>
            <Contact />
          </Suspense>
        } />
        
        <Route path="dashboard" element={
          <Suspense fallback={<Loading />}>
            <Dashboard />
          </Suspense>
        } />
        
        <Route path="*" element={
          <Suspense fallback={<Loading />}>
            <NotFound />
          </Suspense>
        } />
      </Route>
    </Routes>
  );
}

export default App;

その他の便利なフック

React Router v6では、以下のような便利なフックも提供されています:

  1. useLocation: 現在のURLの情報を取得
  2. useMatch: 特定のパスがマッチするか確認
  3. useOutlet: 子ルートのコンポーネントにアクセス
  4. useRoutes: JSでルート設定を定義

例えば、useLocationを使って、ページビューをアナリティクスに送信できます:

// App.jsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();
  
  useEffect(() => {
    // ページ遷移時にアナリティクスにイベントを送信
    // analytics.pageView(location.pathname);
    console.log(`ページビュー: ${location.pathname}`);
  }, [location]);
  
  // ...
}

まとめ

React Router v6は、Reactアプリケーションのルーティングを簡単かつ柔軟に実装できるライブラリです。この記事で紹介した基本的な使い方から応用テクニックまでをマスターすれば、洗練されたSPAを構築することができます。

以下に、React Router v6の主なメリットをまとめます:

  • 宣言的なルーティング定義
  • ネストされたルートの簡単な実装
  • 強力なHooks API
  • 動的なパラメータ処理
  • プログラムによるナビゲーション
  • パフォーマンス最適化機能

実際のプロジェクトでは、ここで紹介した機能を組み合わせて、ユーザーフレンドリーなナビゲーション体験を提供しましょう。

参考リソース

コメント

タイトルとURLをコピーしました