はい、承知いたしました。Reactアプリケーションにおけるreact-router-domを使ったルーティングの基本について、約5000語の詳細な解説記事を作成します。記事の内容を直接表示します。
Reactでルーティング!react-router-domの基本 の詳細な説明
はじめに:ウェブアプリケーションにおけるルーティングの重要性
現代のウェブアプリケーションは、もはや単一の静的なページで構成されることは稀です。ユーザーはアプリケーション内で様々な機能や情報にアクセスし、その度に異なる「画面」へと切り替わるのが一般的です。例えば、オンラインショッピングサイトであれば、トップページ、商品一覧ページ、商品詳細ページ、カートページ、注文履歴ページなど、多くの画面が存在します。これらの画面間の遷移を管理し、それぞれの画面に固有のURLを割り当て、ユーザーがそのURLを使って直接特定の画面にアクセスできるようにする仕組みが「ルーティング」です。
ルーティングは、ウェブアプリケーションのユーザビリティと機能性において非常に重要な役割を果たします。
- ナビゲーション: ユーザーがアプリケーション内を自由に移動できるようにします。
- ブックマークと共有: 特定の画面のURLをブックマークしたり、他者と共有したりすることを可能にします。
- 履歴管理: ブラウザの「戻る」「進む」ボタンで、ユーザーがたどってきた画面の履歴を管理します。
- 初期表示: URLに基づいて、アプリケーションの初回ロード時に適切な画面を表示します。
従来のウェブサイトは、画面遷移の度にサーバーにリクエストを送り、新しいHTMLページをブラウザが受け取ってレンダリングする、いわゆるマルチページアプリケーション(MPA)が主流でした。この場合、ルーティングは主にサーバーサイドの役割であり、どのURLにアクセスされたかによってサーバーが返すHTMLを決定していました。
しかし、Reactのようなライブラリやフレームワークを用いた、いわゆるシングルページアプリケーション(SPA)が普及するにつれて、ルーティングの考え方も変化しました。
なぜSPAにルーティングが必要か?そしてその課題
SPAは、最初のページロード時に必要なHTML、CSS、JavaScriptなどのリソースをまとめて(または必要に応じて少しずつ)ロードし、その後の画面切り替えはサーバーからの新しいHTMLの取得を伴わず、JavaScriptによって動的にDOMを操作することで実現します。これにより、MPAと比較して高速でスムーズな画面遷移や、ネイティブアプリケーションに近いリッチなユーザー体験を提供できます。
しかし、SPAは新しいHTMLページをサーバーから取得しないため、MPAのサーバーサイドルーティングをそのまま利用することはできません。ブラウザのアドレスバーのURLは初期ロード時のものから変化せず、単にDOMの内容だけが書き換わってしまうという問題が発生します。これは以下のような課題を引き起こします。
- 履歴管理の喪失: ブラウザの「戻る」「進む」ボタンが正しく機能しません。常に初期ロードされたページに戻ってしまい、SPA内部での画面遷移の履歴を辿れません。
- ブックマークと共有の不便: 特定の画面の状態を示す固有のURLが存在しないため、その画面をブックマークしたり、他者と共有したりすることが困難です。共有したい場合は、口頭で「トップページからこのボタンを押して、次にここへ行って…」のように操作手順を説明する必要が出てきます。
- リロード時の問題: SPA内部の特定の画面を表示しているときにページをリロードすると、URLは初期ロード時のままであるため、アプリケーションは初期状態に戻ってしまいます。ユーザーが期待する画面が再表示されません。
- 初期表示の制限: URLによる特定の画面への直接アクセスができません。常にアプリケーションの最初の画面から操作を開始する必要があります。
これらの課題を解決するために、SPAではブラウザ側、すなわちクライアントサイドでルーティングを管理する仕組みが必要になります。これが「クライアントサイドルーティング」です。クライアントサイドルーティングでは、JavaScriptがブラウザのHistory APIなどを利用してURLを操作し、そのURLの変更に応じてアプリケーションの表示内容を切り替えます。サーバーへのリクエストは発生しません(APIデータの取得などは別途行われます)。
react-router-domとは?
Reactエコシステムにおいて、クライアントサイドルーティングを実現するためのデファクトスタンダードとして広く利用されているライブラリが react-router-dom です。
react-router はルーティングのコア機能(URLのマッチング、履歴スタックの管理など)を提供するライブラリであり、react-router-dom はそれに加えてウェブブラウザ環境(DOM)でルーティングを行うための機能(<Link>、<BrowserRouter>など)を提供するライブラリです。通常、Reactでウェブアプリケーションを開発する場合は react-router-dom をインストールして使用します。
react-router-dom を使うことで、Reactコンポーネントを使って宣言的にルーティングを定義できます。つまり、「このパスの時はこのコンポーネントを表示する」といったルールを、JSXの記述の中に自然に組み込むことができます。これにより、ルーティングの設定がアプリケーションのUI構造と密接に連携し、理解しやすく保守しやすいコードになります。
主な特徴:
- 宣言的なAPI: JSXを使って、コンポーネントベースでルーティングを記述できます。
- コンポーネントとの連携: ルーティングの情報(現在のパス、URLパラメータなど)をReactコンポーネント内で簡単に利用できます。
- 履歴管理: ブラウザの履歴スタックと連携し、「戻る」「進む」操作をサポートします。
- ネストされたルーティング: コンポーネントの階層構造に対応したルーティングを簡単に構築できます。
- プログラムによるナビゲーション: ボタンクリック時など、イベントに応じてJavaScriptコードから画面遷移を実行できます。
react-router-dom のインストール
react-router-dom を使い始めるには、まずプロジェクトにライブラリをインストールする必要があります。npmまたはyarnを使ってインストールできます。
“`bash
npmの場合
npm install react-router-dom
yarnの場合
yarn add react-router-dom
“`
これで、react-router-dom が提供するコンポーネントやフックをあなたのReactアプリケーションで使用できるようになります。
react-router-dom の基本コンポーネント
react-router-dom は、アプリケーションにルーティング機能を追加するためにいくつかの主要なコンポーネントを提供します。これらのコンポーネントを組み合わせることで、ルーティングの定義、ナビゲーション、そして現在のURLに応じたコンポーネントのレンダリングを行います。
1. ルーターコンポーネント
アプリケーション全体(またはルーティングを管理したい部分)をこれらのルーターコンポーネントでラップする必要があります。これにより、コンポーネントツリー全体がルーティングのコンテキストを持つようになります。
-
<BrowserRouter>:
HTML5 History API (pushState,replaceState,popstateイベントなど) を使用して、ブラウザのURL履歴を管理します。これにより、URLが/users/123のようにクリーンな形式になります。モダンなウェブアプリケーションで最も一般的に使用されるルーターです。
ブラウザのHistory APIを使用するため、リロード時や直接URLにアクセスした場合に、サーバー側でそのURLに対応するレスポンスを返す(通常は単一のindex.htmlを返すように設定する)必要があります。そうしないと、サーバーがそのパスを見つけられずに404エラーとなる可能性があります。 -
<HashRouter>:
URLのハッシュ部分(#以降の部分)を使用してルーティングを管理します。URLは/users#/123のようになります。ハッシュ部分はサーバーに送られないため、サーバーサイドの設定なしにクライアントサイドルーティングを実現できます。静的なファイルサーバーでホストされるアプリケーションや、History API がサポートされていないレガシーなブラウザ環境などで使用されることがあります。しかし、URLの見栄えが悪く、History API に比べて機能が制限されるため、特別な理由がない限りは<BrowserRouter>を使用することが推奨されます。 -
MemoryRouter(主にテストや非ブラウザ環境):
URL履歴をメモリ内に保持します。URLとやり取りする必要がないテスト環境や、React Nativeのような非ブラウザ環境でのルーティングに使用されます。ウェブアプリケーション開発で直接使用することはほとんどありません。
どのルーターを使うべきか?
ほとんどの現代的なウェブアプリケーションでは、<BrowserRouter> が推奨されます。クリーンなURLとHistory API の機能を利用できます。ただし、アプリケーションをホストするサーバー側で、ルーティングに関係なくすべてのリクエストを index.html にフォールバックさせる設定が必要になる点を覚えておきましょう。
配置方法:
ルーターコンポーネントは、アプリケーションのルートコンポーネント、つまりルーティングを管理したいコンポーネントツリーの一番外側で一度だけ配置します。
“`jsx
// src/index.js または src/App.js など
import React from ‘react’;
import ReactDOM from ‘react-dom/client’; // または ‘react-dom’
import { BrowserRouter } from ‘react-router-dom’;
import App from ‘./App’;
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
);
“`
2. ルート定義コンポーネント
どのパスにアクセスされたときにどのコンポーネントをレンダリングするかを定義します。
-
<Routes>(v6 以降):
複数の<Route>要素をグループ化し、現在のURLに最も適した(マッチした)最初の<Route>だけをレンダリングするために使用します。これはreact-router-domv6 で導入された重要なコンポーネントです。v5までの<Switch>コンポーネントに代わるものです。
<Routes>が導入されたことで、<Route>のマッチングロジックがより直感的になり、デフォルトで「最も具体的な(exact マッチングに近い)ルートが優先される」という挙動になりました。これにより、v5で必要だったexactプロパティのほとんどが不要になりました。 -
<Route>:
個々のルーティングルールを定義します。少なくともpathプロパティと、そのパスにマッチした場合にレンダリングする要素(コンポーネント)を指定する必要があります。path(string): マッチさせたいURLのパスを指定します。/about,/users/:userId,*などの形式があります。element(React.ReactNode, v6 以降):pathにマッチした場合にレンダリングするReact要素を指定します。<Home />や<About />のようにJSXで直接指定します。v5ではcomponentやrenderプロパティを使用しましたが、v6ではelementに統一されました。これは、コンポーネントを渡すのではなく「レンダリング結果の要素」を渡すことで、react-router-domがレンダリングの最適化をより効率的に行えるように変更されたためです。
v5 から v6 への変更点 (Switch から Routes, component/render から element)
v5では、複数の <Route> を囲むのに <Switch> を使用し、exact プロパティを使って厳密なパス一致を指定することが一般的でした。
“`jsx
// v5 の例
import { Switch, Route } from ‘react-router-dom’;
import Home from ‘./Home’;
import About from ‘./About’;
function App() {
return (
{/ … 他のルート /}
);
}
“`
v6では、<Routes> が複数の <Route> を囲み、element プロパティを使用します。exact は不要になりました。<Routes> 内では、より具体的なパス(長いパスやパラメータを含むパス)が、より一般的なパス(短いパスやルートパス /)よりも優先してマッチするようになりました。
“`jsx
// v6 の例
import { Routes, Route } from ‘react-router-dom’;
import Home from ‘./Home’;
import About from ‘./About’;
function App() {
return (
{/ … 他のルート /}
);
}
“`
3. ナビゲーションコンポーネントとフック
ユーザーがアプリケーション内で異なるルートに移動するための手段を提供します。
-
<Link>:
ウェブ標準の<a>タグの代替として使用されます。<Link>がレンダリングされると、内部的にはやはり<a>タグになりますが、クリックされた際にページ全体のリロードを発生させる代わりに、History API を利用してURLを変更し、react-router-domに表示するコンポーネントを切り替えさせます。これにより、SPAらしいスムーズな画面遷移を実現します。to(string | object): 遷移先のパスを指定します。文字列 ("/about") またはオブジェクト ({ pathname: "/users", search: "?sort=name", hash: "#settings", state: { fromDashboard: true } }) で指定できます。オブジェクト形式を使うと、クエリパラメータ、ハッシュ、ステート(履歴に紐づける追加情報)なども含めることができます。replace(boolean, optional):trueに設定すると、現在の履歴スタックのエントリを置き換えます。false(デフォルト) の場合、新しいエントリを履歴スタックに追加します。例えば、ログインページからダッシュボードに遷移する際にreplace={true}を使うと、「戻る」ボタンでログインページに戻れなくなります。
“`jsx
import { Link } from ‘react-router-dom’;function Navigation() {
return ();
}
“` -
<NavLink>:
<Link>とほぼ同じですが、現在のURLがそのリンクのtoプロパティにマッチしている場合に、追加のクラス名やスタイルを適用できる機能を持っています。ナビゲーションメニューで、現在アクティブなページを示すために便利です。to(string | object):<Link>と同じ。className(string | function, v6 以降): マッチした場合に適用されるクラス名。文字列または関数で指定できます。関数を使うと、アクティブ状態かどうかに応じて動的にクラス名を決定できます。style(object | function, v6 以降): マッチした場合に適用されるスタイル。オブジェクトまたは関数で指定できます。end(boolean, v6 以降):trueに設定すると、リンクのtoパスが現在のURLの末尾に完全に一致する場合にのみアクティブになります。デフォルトはfalseで、リンクのtoパスが現在のURLの先頭に一致すればアクティブになります。例えば、/usersリンクがあるとき、/users/123というURLでもデフォルトではアクティブになりますが、end={true}を指定すると/usersの時だけアクティブになります。
“`jsx
import { NavLink } from ‘react-router-dom’;function Navigation() {
// アクティブな時に “active” クラスを適用する例 (関数形式)
const activeClassName = ({ isActive }) => isActive ? “active” : “”;// アクティブな時に特定のスタイルを適用する例 (関数形式)
const activeStyle = ({ isActive }) => ({
fontWeight: isActive ? “bold” : “”,
color: isActive ? “red” : “”,
});return (
);
}
“` -
useNavigateフック (v6 以降):
コンポーネントの内部で、ユーザーの操作などに応じてプログラム的にナビゲーション(画面遷移)を行いたい場合に使用します。例えば、フォーム送信後に別のページにリダイレクトしたい場合や、ボタンクリックで詳細ページに遷移したい場合などに使います。v5のuseHistoryフックに代わるものです。useNavigateフックを呼び出すと、ナビゲーションを実行するための関数が返されます。“`jsx
import { useNavigate } from ‘react-router-dom’;function MyComponent() {
const navigate = useNavigate(); // navigate 関数を取得const handleClick = () => {
// ボタンクリックで /dashboard に遷移
navigate(‘/dashboard’);
};const handleFormSubmit = (data) => {
// フォーム送信後、ステートと置き換えオプション付きで /success に遷移
navigate(‘/success’, { replace: true, state: { formData: data } });
};const goBack = () => {
// 一つ前のページに戻る (ブラウザの戻るボタンと同じ)
navigate(-1);
};const goForward = () => {
// 一つ先のページに進む (ブラウザの進むボタンと同じ)
navigate(1);
};return (
);
}
“`navigate関数は、遷移先のパス(文字列またはオブジェクト)、およびオプションとしてreplaceやstateを受け取ります。また、数値(-1や1)を渡すことで履歴を操作することも可能です。
基本的なルーティングの実装例
これらの基本コンポーネントを使って、簡単なアプリケーションにルーティング機能を実装してみましょう。
アプリケーションの構成:
src/
├── App.js
├── index.js
├── components/
│ ├── Header.js
│ └── Navigation.js
├── pages/
│ ├── Home.js
│ ├── About.js
│ └── Contact.js
└── index.css (任意)
まず、各ページコンポーネントを作成します。シンプルに、それぞれのページ名を表示するだけにします。
src/pages/Home.js
“`jsx
import React from ‘react’;
function Home() {
return (
Home Page
Welcome to the home page!
);
}
export default Home;
“`
src/pages/About.js
“`jsx
import React from ‘react’;
function About() {
return (
About Page
Learn more about us.
);
}
export default About;
“`
src/pages/Contact.js
“`jsx
import React from ‘react’;
function Contact() {
return (
Contact Page
Get in touch with us.
);
}
export default Contact;
“`
次に、ナビゲーションコンポーネントを作成します。ここでは <NavLink> を使用して、アクティブなリンクにスタイルを適用できるようにします。
src/components/Navigation.js
“`jsx
import React from ‘react’;
import { NavLink } from ‘react-router-dom’;
// 必要であればスタイルシートをインポート
// import ‘./Navigation.css’;
function Navigation() {
// アクティブなリンクに適用するスタイルを関数で定義 (v6形式)
const activeStyle = ({ isActive }) => ({
fontWeight: isActive ? “bold” : “normal”,
textDecoration: isActive ? “underline” : “none”,
});
return (
);
}
export default Navigation;
“`
アプリケーションのメインコンポーネント (App.js) で、ルーター、ナビゲーション、そしてルート定義を配置します。
src/App.js
“`jsx
import React from ‘react’;
import { Routes, Route } from ‘react-router-dom’; // Routes と Route をインポート
import Navigation from ‘./components/Navigation’; // ナビゲーションコンポーネント
import Home from ‘./pages/Home’; // 各ページコンポーネント
import About from ‘./pages/About’;
import Contact from ‘./pages/Contact’;
function App() {
return (
Simple React App with Routing
{/* Routes コンポーネントでルート定義を囲む */}
<Routes>
{/* 各 Route コンポーネントでパスと表示する要素を定義 */}
{/* path="/" にマッチしたら Home コンポーネントを表示 */}
<Route path="/" element={<Home />} />
{/* path="/about" にマッチしたら About コンポーネントを表示 */}
<Route path="/about" element={<About />} />
{/* path="/contact" にマッチしたら Contact コンポーネントを表示 */}
<Route path="/contact" element={<Contact />} />
{/* ルートが見つからなかった場合のフォールバックルート (オプション) */}
{/* path="*" は、上記のどのルートにもマッチしなかった場合にマッチします */}
<Route path="*" element={<div>404 Not Found</div>} />
</Routes>
</div>
);
}
export default App;
“`
最後に、アプリケーション全体を <BrowserRouter> でラップします。これは通常 src/index.js で行います。
src/index.js
“`jsx
import React from ‘react’;
import ReactDOM from ‘react-dom/client’; // または ‘react-dom’
import { BrowserRouter } from ‘react-router-dom’; // BrowserRouter をインポート
import App from ‘./App’;
import ‘./index.css’; // 任意のグローバルスタイル
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
);
“`
これで基本的なルーティングが実装されました。アプリケーションを実行し、ナビゲーションリンクをクリックしたり、ブラウザのアドレスバーでURLを変更したり、戻る/進むボタンを使ってみてください。ページ全体のリロードなしにコンテンツが切り替わり、URLが更新され、履歴が管理されることが確認できます。
ルートパラメータ (useParams)
ユーザーIDや商品IDなど、URLの一部として動的な値を渡したい場合があります。例えば、/users/123 や /products/abc のようなURLです。react-router-dom では、ルート定義に「ルートパラメータ」を含めることで、これを実現できます。
ルートパラメータは、path プロパティの中で :パラメータ名 の形式で指定します。
jsx
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/products/:productId" element={<ProductDetail />} />
マッチしたルートコンポーネント内で、そのパラメータの値を取得するには useParams フック (v6) を使用します。useParams は、URLから抽出されたパラメータをキーと値のペアとして持つオブジェクトを返します。
“`jsx
// src/pages/UserProfile.js
import React from ‘react’;
import { useParams } from ‘react-router-dom’; // useParams フックをインポート
function UserProfile() {
// URLからパラメータを取得
// 例: パスが “/users/:userId” の場合、params.userId で値を取得できる
const params = useParams();
const userId = params.userId; // または分割代入で const { userId } = useParams();
// ここで取得した userId を使ってユーザー情報をフェッチしたり表示したりする
return (
User Profile
User ID: {userId}
{/ ユーザー情報表示ロジック /}
);
}
export default UserProfile;
“`
このように、useParams フックを使うことで、URLの動的な部分を簡単にコンポーネント内で利用できます。
クエリパラメータとステート (useSearchParams, useLocation)
URLには、パスの他にクエリパラメータ (?key=value&anotherKey=anotherValue) やハッシュ (#section)、さらには react-router-dom が独自に管理できる「ステート」情報を含めることができます。これらの情報も、特定の画面でフィルタリングやソート条件を渡したり、遷移元の情報を保持したりするのに役立ちます。
クエリパラメータ (useSearchParams – v6)
クエリパラメータは、URLの ? 以降の部分です。検索結果ページでの検索語 (/search?q=react) や、商品一覧ページでのフィルタリング・ソート条件 (/products?category=electronics&sort=price) などによく使用されます。
react-router-dom v6 では、クエリパラメータを扱うための useSearchParams フックが導入されました。このフックは、URLSearchParams オブジェクトと、クエリパラメータを更新するための関数をペアで返します。
“`jsx
import React, { useEffect } from ‘react’;
import { useSearchParams } from ‘react-router-dom’; // useSearchParams をインポート
function SearchResults() {
// useSearchParams は [URLSearchParams オブジェクト, セッター関数] を返す
const [searchParams, setSearchParams] = useSearchParams();
// クエリパラメータから値を取得
const query = searchParams.get(‘q’); // ?q=react の場合、’react’ を取得
const sortBy = searchParams.get(‘sort’); // ?sort=price の場合、’price’ を取得
const category = searchParams.get(‘category’); // パラメータがない場合は null
useEffect(() => {
// query, sortBy, category などが変更されたときに検索を実行するなどの副作用
console.log(‘検索語:’, query);
console.log(‘ソート条件:’, sortBy);
console.log(‘カテゴリ:’, category);
// 例: APIを呼び出す fetchSearchResults(query, sortBy, category);
}, [query, sortBy, category]); // 依存配列にパラメータを含める
// クエリパラメータを更新する例
const handleSortChange = (event) => {
const newSort = event.target.value;
// クエリパラメータを更新。これによりURLが変化し、コンポーネントが再レンダリングされる
// setSearchParams({ q: query, sort: newSort, category: category }); // 全体を置き換える場合
// 既存のパラメータを維持しつつ、一部を更新する場合 (URLSearchParamsオブジェクトのメソッドを利用)
const newSearchParams = new URLSearchParams(searchParams); // 現在のパラメータで初期化
newSearchParams.set(‘sort’, newSort); // sort だけ更新
setSearchParams(newSearchParams); // または setSearchParams(newSearchParams.toString());
};
return (
Search Results
Query: {query || ‘N/A’}
Sort By: {sortBy || ‘Default’}
Category: {category || ‘All’}
<select onChange={handleSortChange} value={sortBy || ''}>
<option value="">Default</option>
<option value="price">Price</option>
<option value="date">Date</option>
</select>
{/* 検索結果の表示 */}
</div>
);
}
“`
URLSearchParams オブジェクトは、クエリ文字列を簡単に操作するための便利なメソッド(get, set, append, delete, toString など)を提供します。setSearchParams 関数に新しい URLSearchParams オブジェクトまたはクエリ文字列を渡すことで、URLのクエリパラメータを更新できます。URLが更新されると、そのコンポーネント(またはそのURLにマッチする他のコンポーネント)が再レンダリングされ、useSearchParams から返される値も最新のものになります。
ステート (useLocation – v6)
react-router-dom は、URLには表示されないが、特定のナビゲーションに関連付けたい任意の情報を「ステート」として履歴エントリに保存する機能を提供します。これは、例えば遷移元のページ情報や、遷移先のページで使用する一時的なデータ(ただし大量でないもの)を渡すのに便利です。
ステートは、<Link> の to プロパティにオブジェクトとして渡すか、useNavigate フックの第2引数のオプションオブジェクトとして渡すことで設定できます。
“`jsx
// Link でステートを渡す例
Proceed to Checkout
// useNavigate でステートを渡す例
const navigate = useNavigate();
const handleLoginSuccess = (userData) => {
navigate(‘/dashboard’, { state: { loggedInUser: userData, from: ‘/login’ } });
};
“`
現在のルートに関連付けられたステート情報を受け取るには、useLocation フックを使用します。useLocation は、現在のURLに関する情報(pathname, search, hash, state など)を持つ location オブジェクトを返します。
“`jsx
// src/pages/Checkout.js
import React from ‘react’;
import { useLocation } from ‘react-router-dom’; // useLocation をインポート
function Checkout() {
// useLocation から location オブジェクトを取得
const location = useLocation();
// location.state に遷移時に渡されたステート情報が含まれる
const { cartItems, total } = location.state || {}; // ステートがない場合も考慮してデフォルト値を指定
if (!cartItems || cartItems.length === 0) {
// ステートがない、またはカートが空の場合の処理 (例: ホームに戻す)
// return
// または
// const navigate = useNavigate();
// useEffect(() => { navigate(‘/’, { replace: true }); }, [navigate]);
return
No items in cart or invalid access.
;
}
return (
Checkout
Order Summary:
-
{cartItems.map((item, index) =>
- {item}
)}
Total: ${total}
{/ 決済処理など /}
);
}
“`
location.state は、ナビゲーション時以外(例えば、ブラウザのリロード時や直接URLアクセス時)は undefined になる可能性があることに注意が必要です。そのため、ステートに依存するロジックでは、ステートが存在しない場合のハンドリングを適切に行う必要があります。クエリパラメータとは異なり、ステートはURLの一部ではないため、サーバーへのリクエストに含まれず、リロード時には失われます。
ネストされたルーティング (<Outlet>)
多くのアプリケーションでは、画面全体が切り替わるだけでなく、画面の一部だけがURLに応じて切り替わるという構造が見られます。例えば、ユーザー設定画面の中に「プロフィール設定」「アカウント設定」「通知設定」といったタブがあり、それぞれのタブに対応するURL(例: /settings/profile, /settings/account, /settings/notifications)が存在する場合です。この場合、ヘッダーやサイドバー、設定画面のメインレイアウト部分は共通で、中央のコンテンツ領域だけが切り替わります。これを実現するのが「ネストされたルーティング」です。
react-router-dom v6 では、ネストされたルーティングは親ルートと子ルートの定義、そして親コンポーネント内の <Outlet> コンポーネントを使って実現します。
- 親コンポーネント内で子ルートの表示位置を指定: 親ルートに対応するコンポーネント内で、子ルートがレンダリングされるべき場所に
<Outlet>コンポーネントを配置します。 - ルート定義の階層化:
<Routes>の中で、<Route>をネストして配置することで、親子の関係を表現します。子<Route>のpathは、通常、親ルートからの相対パスで定義します。
例として、ユーザーダッシュボードに「プロフィール」「設定」「メッセージ」というサブページがあるケースを考えます。
src/pages/Dashboard.js (親コンポーネント)
“`jsx
import React from ‘react’;
import { Link, Outlet } from ‘react-router-dom’; // Outlet をインポート
function Dashboard() {
return (
User Dashboard
<hr />
{/* ここにネストされたルートにマッチした子コンポーネントがレンダリングされる */}
<Outlet />
</div>
);
}
export default Dashboard;
“`
src/pages/DashboardProfile.js (子コンポーネント例)
“`jsx
import React from ‘react’;
function DashboardProfile() {
return (
Profile
View and edit your profile information.
{/ プロフィール表示・編集フォームなど /}
);
}
export default DashboardProfile;
“`
src/pages/DashboardSettings.js (子コンポーネント例)
“`jsx
import React from ‘react’;
function DashboardSettings() {
return (
Settings
Manage your account settings.
{/ 設定項目など /}
);
}
export default DashboardSettings;
“`
src/pages/DashboardMessages.js (子コンポーネント例)
“`jsx
import React from ‘react’;
function DashboardMessages() {
return (
Messages
View your messages.
{/ メッセージリストなど /}
);
}
export default DashboardMessages;
“`
src/App.js (ルート定義)
“`jsx
import React from ‘react’;
import { Routes, Route } from ‘react-router-dom’;
import Navigation from ‘./components/Navigation’; // 共通ナビゲーション
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import Contact from ‘./pages/Contact’;
import Dashboard from ‘./pages/Dashboard’; // 親コンポーネント
import DashboardProfile from ‘./pages/DashboardProfile’; // 子コンポーネント
import DashboardSettings from ‘./pages/DashboardSettings’; // 子コンポーネント
import DashboardMessages from ‘./pages/DashboardMessages’; // 子コンポーネント
function App() {
return (
Simple React App with Routing
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
{/* ダッシュボードルート (親ルート) */}
{/* path="/dashboard/*" とすることで、"/dashboard" に続くパスもこのルートで処理されるようになる (v5の exact={false} に近い考え方) */}
{/* あるいは親 Route にはパスを付けず、子 Route に完全なパスを書く方法もある */}
{/* v6では、親Routeに path="/dashboard" とだけ書き、子Routeの path="profile" を相対パスとするのが一般的 */}
<Route path="/dashboard" element={<Dashboard />}> {/* 親ルート定義 */}
{/* ネストされたルート定義 (子ルート) */}
{/* 親パス(/dashboard)からの相対パスで定義 */}
{/* path="profile" は /dashboard/profile にマッチ */}
<Route path="profile" element={<DashboardProfile />} />
<Route path="settings" element={<DashboardSettings />} />
<Route path="messages" element={<DashboardMessages />} />
{/* /dashboard にアクセスした場合のデフォルト子ルート */}
{/* index プロパティを使用 (v6) */}
<Route index element={<DashboardProfile />} /> {/* /dashboard にマッチした時に /dashboard/profile と同じ内容を表示したい場合 */}
{/* /dashboard に続くパスが見つからなかった場合のフォールバック */}
{/* <Route path="*" element={<div>Dashboard Sub-Page Not Found</div>} /> */}
</Route>
{/* 他のルートが見つからなかった場合のフォールバック */}
<Route path="*" element={<div>404 Not Found</div>} />
</Routes>
</div>
);
}
export default App;
“`
この設定により、以下の挙動が実現されます。
/dashboardにアクセスすると、Dashboardコンポーネントがレンダリングされます。<Outlet>の位置には、indexプロパティを持つ子ルートがマッチするため、DashboardProfileコンポーネントがレンダリングされます。/dashboard/profileにアクセスすると、Dashboardコンポーネントがレンダリングされ、<Outlet>の位置にはDashboardProfileコンポーネントがレンダリングされます。/dashboard/settingsにアクセスすると、Dashboardコンポーネントがレンダリングされ、<Outlet>の位置にはDashboardSettingsコンポーネントがレンダリングされます。/dashboard/messagesにアクセスすると、Dashboardコンポーネントがレンダリングされ、<Outlet>の位置にはDashboardMessagesコンポーネントがレンダリングされます。
ネストされたルーティングは、複雑なUIを持つアプリケーションにおいて、コードの構造化と管理を容易にします。
インデックスルートとNotFoundページ (index, *)
インデックスルート (index)
ネストされたルートを持つ親パスに、子パスを指定せず直接アクセスした場合(例: /dashboard のような /dashboard/profile のような子パスがない状態)、どのコンポーネントを表示すべきか指定したい場合があります。react-router-dom v6 では、子 <Route> に index プロパティを true に設定することで、これを実現します。
jsx
<Route path="/dashboard" element={<Dashboard />}>
{/* ... 他の子ルート ... */}
{/* /dashboard にマッチした時のデフォルト表示 */}
<Route index element={<DashboardProfile />} />
</Route>
index ルートは、親ルートのパスが完全に一致し、かつどの子ルートにもマッチしない場合にレンダリングされます。これにより、例えば /users というURLでユーザー一覧を表示し、/users/:userId でユーザー詳細を表示するような場合、/users をリスト表示用のインデックスルートとして設定することができます。
NotFoundページ (*)
要求されたURLが定義されているどのルートにもマッチしなかった場合に、特定のコンポーネント(例えば「404 Not Found」ページ)を表示したい場合があります。これは、path プロパティに * を指定した <Route> を、<Routes> の中に定義されている他のどの <Route> よりも最後に配置することで実現できます。
“`jsx
{/ … 他のルート … /}
{/ どのルートにもマッチしなかった場合のフォールバック /}
} />
“`
<Routes> コンポーネントは、子 <Route> の中で現在のURLに最もマッチするものを一つだけ選択してレンダリングします。* はワイルドカードとして「あらゆるパスにマッチする」という意味ですが、<Routes> のマッチングロジックでは、より具体的なパス(/about, /users/:userId など)が * よりも優先されます。そのため、* のルートを最後に配置することで、他のどのルートにもマッチしなかった場合の最後の手段として機能させることができます。
ナビゲーションの詳細 (<Link>, useNavigate)
これまで基本的な使い方を見てきましたが、ナビゲーションコンポーネントやフックには、さらに便利な機能があります。
<Link> と <NavLink> のプロパティ
to: 最も重要なプロパティで、遷移先のパスを指定します。- 文字列:
/dashboard,/users/123?tab=settingsのようにそのままパスを指定。 - オブジェクト: より詳細な情報を指定できます。
pathname: パス部分 (/users/123)search: クエリパラメータ (?tab=settings)hash: ハッシュ (#profile)state: 付加的なステート情報 ({ from: ‘/old-page’ })
jsx
<Link to={{
pathname: "/users/123",
search: "?tab=settings",
hash: "#profile",
state: { from: location.pathname }
}}>
User Settings (Complex Link)
</Link>
- 文字列:
replace(boolean):trueの場合、現在のエントリを履歴スタックで置き換えます。デフォルトはfalseで、新しいエントリをスタックに追加します。ログイン後リダイレクトなどに便利です。
jsx
<Link to="/dashboard" replace>Go to Dashboard (Replace History)</Link>NavLink固有のプロパティ:className(string | function): アクティブ時に適用するクラス名。関数形式({ isActive, isPending }) => string | undefinedが便利です。isPendingはv6.4以降で追加され、遷移中の状態を示します。style(object | function): アクティブ時に適用するスタイル。関数形式({ isActive, isPending }) => object | undefinedが利用できます。end(boolean):NavLinkのtoが現在のURLの末尾に完全に一致する場合のみアクティブとみなすかどうか。デフォルトはfalse(先頭一致でアクティブ)。親パスのNavLinkが子パスでもアクティブになるのを防ぎたい場合にendをtrueにします。
useNavigate フックの詳細
useNavigate から返される navigate 関数は多機能です。
navigate(to, options): 指定されたパスに遷移します。to(string | number): 遷移先のパス文字列、または履歴スタックを操作するための数値 (-1で戻る、1で進む)。options(object, optional):replace(boolean):trueの場合、現在の履歴エントリを置き換えます。state(any): 遷移先のルートに渡すステート情報。relative(‘route’ | ‘path’, v6.4+): 相対パスの基準を指定。'route'(デフォルト) は現在のルートのパス、'path'は現在のURL全体を基準とします。ネストされたルーティングで子ルート間を移動する際に便利です。
“`jsx
const navigate = useNavigate();
// 基本的な遷移
navigate(‘/new-page’);
// 履歴を置き換えて遷移
navigate(‘/login’, { replace: true });
// ステートを渡して遷移
navigate(‘/checkout’, { state: { item: ‘widget’ } });
// 履歴を戻る
navigate(-1);
// 履歴を進む
navigate(1);
// ネストされた子ルート内での相対パス遷移 (例: /dashboard/profile から /dashboard/settings へ)
// 現在のパスが /dashboard/profile の場合
navigate(‘../settings’); // 相対パスで親のsettingsへ。結果 /dashboard/settings に遷移
navigate(‘messages’, { relative: ‘route’ }); // 現在のルート /dashboard からの相対パス。結果 /dashboard/messages に遷移
“`
useNavigate は、イベントハンドラや useEffect 内など、コンポーネントのレンダリング中に直接実行できない場所からナビゲーションを行いたい場合に非常に便利です。
より高度な機能 (概要)
react-router-dom は、さらに多くの機能を提供しています。ここではその一部を概要として紹介します。
-
ルート設定のオブジェクト化 (
useRoutes– v6):
特に大規模なアプリケーションでルートが増えてくると、JSXで<Routes>と<Route>をネストしていくと見通しが悪くなることがあります。useRoutesフックを使用すると、ルート定義をJavaScriptオブジェクトの配列として管理し、それを元にルート要素をレンダリングできます。これにより、ルート設定を別のファイルに分離したり、動的に生成したりするのが容易になります。“`jsx
import { useRoutes } from ‘react-router-dom’;
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
// … 他のコンポーネント// ルート定義オブジェクト
const routes = [
{ path: ‘/’, element:},
{ path: ‘/about’, element:},
{ path: ‘/users/:userId’, element:},
{
path: ‘/dashboard’,
element:,
children: [ // ネストされたルートは children プロパティで定義
{ index: true, element:},
{ path: ‘settings’, element:},
],
},
{ path: ‘*’, element:404 Not Found},
];function App() {
const element = useRoutes(routes); // オブジェクトからルート要素を生成
return (App Header
{element} {/ 生成されたルート要素をレンダリング /}
);
}
``useRoutesを使う場合、` コンポーネントは不要です。 -
コード分割 (
React.lazyとSuspense):
アプリケーションのバンドルサイズを小さく保ち、初期ロード時間を短縮するために、ルートごとにコンポーネントを遅延ロード(Lazy Loading)することができます。React.lazyを使用してコンポーネントを動的にインポートし、それを<Suspense>で囲むことで、ロードが完了するまでの間にローディング表示を行うことができます。“`jsx
import React, { Suspense } from ‘react’;
import { Routes, Route } from ‘react-router-dom’;
import Navigation from ‘./components/Navigation’;
import Home from ‘./pages/Home’; // Home は通常ロード// Lazy ロードされるコンポーネント
const About = React.lazy(() => import(‘./pages/About’));
const Contact = React.lazy(() => import(‘./pages/Contact’));function App() {
return (App Header
Loading…\ }> {/ Lazy ロード中の表示 /}
} />
{/ element プロパティに Lazy コンポーネントを渡す /}
} />
} />
404 Not Found\
} />
);
}
``/about
これにより、や/contact` ページにアクセスされた時に初めて対応するコンポーネントのJavaScriptコードがロードされるようになり、初期バンドルサイズを削減できます。
認証付きルート (Protected Routes):
特定のルートへのアクセスを、ユーザーがログインしているかどうかに応じて制限したい場合があります。これは、ログイン状態をチェックするラッパーコンポーネントを作成し、その中でログインしていなければログインページなどにリダイレクトするロジックを実装することで実現できます。react-router-dom v6 では <Navigate> コンポーネントを使うのが一般的です。
“`jsx
import React from ‘react’;
import { Routes, Route, Navigate } from ‘react-router-dom’;
import Dashboard from ‘./pages/Dashboard’;
import Login from ‘./pages/Login’;
import useAuth from ‘./hooks/useAuth’; // ログイン状態を管理するカスタムフックを仮定
// 認証が必要なルートのラッパーコンポーネント
function ProtectedRoute({ element }) {
const { isAuthenticated } = useAuth();
// ログインしていなければ /login へリダイレクト
// replace で戻るボタンで元のページに戻れないようにする
return isAuthenticated ? element :
}
function App() {
return (
{/ 認証不要なルート /}
} />
{/* 認証が必要なルート */}
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
<Route path="*" element={<div>404 Not Found</div>} />
</Routes>
);
}
``useLocation` やカスタムフックを組み合わせることで実装可能です。
より複雑な認証フロー(ログイン後に元のページに戻る、ロールベースのアクセス制御など)も、
よくある問題とデバッグ
react-router-dom を使用している際によく遭遇する問題と、その原因・解決策について説明します。
-
<BrowserRouter>が複数存在する:
<BrowserRouter>(または他のルーターコンポーネント) は、アプリケーション内でルートを管理する最上位レベルで一度だけ使用する必要があります。ネストして複数配置すると、予期しない動作やエラーの原因となります。通常はsrc/index.jsまたはsrc/App.jsの一番外側でラップします。 -
リロード時に404エラーになる:
これは、<BrowserRouter>がクライアントサイドのHistory API を使用しているために起こる問題です。ブラウザは/users/123というURLでリロードされた際に、そのパスに対応するファイル(例えばusers/123.html)をサーバーに要求します。しかし、SPAではこのようなファイルは存在せず、通常は単一のindex.htmlだけが存在します。サーバーが/users/123というパスを解決できないため、404エラーを返します。
解決策: アプリケーションをホストするウェブサーバー(またはCDN)の設定を変更し、存在しないパスへのリクエストはすべてアプリケーションのエントリーポイントであるindex.htmlを返すように設定します。これにより、ブラウザは常にindex.htmlをロードし、その中のJavaScriptがreact-router-domを起動して、現在のURL (/users/123) に基づいて適切なコンポーネントをクライアントサイドでレンダリングします。
設定方法はサーバー(Nginx, Apache, Express, Netlify, Vercel, Firebase Hostingなど)によって異なります。例えば、Nginxならtry_files $uri $uri/ /index.html;、Apacheなら.htaccessにFallbackResource /index.htmlのような設定を追加します。開発時には、開発サーバー(create-react-appや Viteなどが提供するもの)がこの設定を自動的に行っていることが多いです。 -
v5 と v6 のAPIの違いによる混乱:
react-router-domv6 は、v5 からいくつかの大きな変更(Switch->Routes,component/render->element,useHistory->useNavigate,exact不要化など)がありました。古いドキュメントやチュートリアルを参照していると、v6 では動作しないコードに出くわす可能性があります。
解決策: 公式ドキュメント(特に v6 のもの)を参照することを強く推奨します。また、既存のv5プロジェクトをv6に移行する際には、公式の移行ガイドを参照しながら慎重に進めましょう。 -
パスのマッチングが期待通りにならない:
<Routes>はデフォルトで「最も具体的な」パスにマッチする<Route>を優先します。例えば、/users/:userIdと/users/newというパスがある場合、/users/newは後者により具体的にマッチするため、そちらが優先されます。v5 のexactが不要になったのはこのためです。しかし、意図しないマッチングが起こる場合は、パスの順番を見直したり、必要に応じてpathの定義を調整したりしてください。ワイルドカード (*) ルートは必ず最後に配置します。 -
ネストされたルートで親コンポーネントが再レンダリングされない:
ネストされた子ルートへの遷移は、親コンポーネント(<Outlet>を含むコンポーネント)自身は再レンダリングしない場合があります。useLocationやuseParamsの値は変更されますが、親コンポーネントのライフサイクルは影響を受けにくいことがあります。子ルートのデータ取得などを親で行っている場合は、useLocationやuseParamsの値の変更をuseEffectの依存配列に含めるなどして、適切に副作用を発生させる必要があります。 -
<Link>やnavigateで遷移できない、またはページ全体がリロードされる:<Link>が意図せずページ全体をリロードしてしまう場合、それはおそらく<Link>コンポーネントではなく標準の<a>タグを使用しているか、または<Link>の外側でevent.preventDefault()が呼び出されていないなどの理由でデフォルトのイベントが発生している可能性があります。react-router-domの<Link>を正しくインポートして使用しているか確認してください。useNavigateは Reactコンポーネントまたはカスタムフックの内部から呼び出す必要があります。クラスコンポーネントで使いたい場合は、withRouterHOC (v5, v6ではuseNavigateなどをラップしたカスタムHOCを自作) を使うか、関数コンポーネントに書き換えるのが一般的です。
これらの問題は、react-router-dom のドキュメントをよく読み、コンポーネントの役割とHistory API の基本的な動作を理解することで、多くの場合解決できます。React Developer Tools を使って、コンポーネントツリーや各コンポーネントに渡される props (location, match など – v5, v6では Context や Hooks 経由) を確認することもデバッグに役立ちます。
v5からv6への移行について (概要)
react-router-dom v6 は v5 から大幅な変更があり、APIがよりシンプルで一貫性のあるものになりましたが、移行にはコードの修正が必要です。主な変更点をまとめます。
<Switch>-><Routes>: 複数のルートの中から一つだけを選択してレンダリングする役割が<Routes>に変更されました。component,render->element:<Route>にレンダリングする要素を渡す際に、componentやrenderプロパティは廃止され、element={<Component />}の形式に統一されました。exact不要論:<Routes>の新しいマッチングアルゴリズムにより、ほとんどの場合exactプロパティは不要になりました。より具体的なパスが優先されます。useHistory->useNavigate: プログラムによるナビゲーションを行うフックがuseHistoryからuseNavigateに変更されました。返される関数やその使い方も少し異なります。useRouteMatch->useMatch: 現在の場所が特定のパスにマッチするかどうかを調べるフックがuseRouteMatchからuseMatchに変更されました。- ネストされたルーティングの変更:
<Outlet>コンポーネントと、子<Route>の相対パス定義が中心となりました。v5のネストの仕組みとは異なります。 - ルートパラメータのパス: v5ではネストされたルートで親パスが自動的に継承されましたが、v6では子ルートの
pathは親ルートからの相対パス(または絶対パス)として明示的に指定する必要があります。 useLocationの変更:locationオブジェクトの構造に若干の変更があります。
移行は手間がかかるかもしれませんが、v6 の方がシンプルで直感的な API になっており、今後の開発やメンテナンスが容易になるため、新しいプロジェクトでは v6 を使用し、既存のプロジェクトも可能な範囲で移行を検討することをお勧めします。公式ドキュメントには詳細な移行ガイドが用意されています。
まとめ
この記事では、Reactアプリケーションにおけるクライアントサイドルーティングの必要性から始まり、react-router-dom ライブラリの基本的な使い方について、主要なコンポーネントやフックを中心に詳細に解説しました。
- SPAにおけるルーティングの課題を解決するために
react-router-domがどのように役立つか。 - アプリケーション全体をラップするルーターコンポーネント (
<BrowserRouter>など)。 - ルート定義の中核となる
<Routes>と<Route>、および v6 での変更点。 - ユーザーがクリックして画面遷移を行うための
<Link>と<NavLink>。 - プログラム的に画面遷移を制御する
useNavigateフック。 - URLから動的な値を取得するための
useParamsフック。 - クエリパラメータとステートを扱うための
useSearchParamsとuseLocationフック。 - 複雑なUI構造に対応するためのネストされたルーティングと
<Outlet>コンポーネント。 - 特定のパスにマッチしない場合のフォールバックとしての404ルート。
- より高度な機能として、
useRoutesによるルート設定のオブジェクト化、React.lazyとSuspenseによるコード分割、認証付きルートの実装方法の概要。 - 開発中によく遭遇する問題とその解決策。
- v5からv6への主な変更点。
react-router-dom は非常に強力で柔軟なライブラリであり、これらの基本を理解することで、複雑なルーティング要件を持つモダンなReactアプリケーションも効率的に構築できるようになります。
今回説明した内容は react-router-dom の基本中の基本ですが、これを土台として、認証、権限管理、ルート遷移時のアニメーション、サーバーサイドルーティングとの連携など、さらに多くの機能をアプリケーションに追加していくことが可能です。
React開発においてルーティングは避けて通れない重要な概念です。この記事が、react-router-dom を使ったルーティング実装の理解を深め、あなたのプロジェクトに役立てるための一助となれば幸いです。ぜひ実際にコードを書きながら、様々な機能を試してみてください。