1. はじめに
今回はUdemyの講座でReact Routerを使ったルーティングを学びました。
学習で書いたコードはv5系のものですが、現在主流のv6との違いも合わせて整理しています。
SPAにおけるページ遷移の基本的な仕組みを理解することが今回のメインテーマです。
この記事のコードはReact Router v5系をベースにしています。
v6との違いは各セクションの末尾に「v6との違い」として補足しています。
2. アプリの全体構成
今回は複数ページを持つSPAを構築しました。
ファイル構成と全体のルーター設定から順番に確認していきます。
2.1 App.jsx
まず、アプリ全体のエントリーポイントとなるApp.jsxです。
App.jsx全体:
import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./router/Router";
function App() {
return (
// BrowserRouterでアプリ全体を囲む
<BrowserRouter>
<div className="App">
{/* グローバルナビゲーション */}
<Link to="/">Home</Link>
<br />
<Link to="/page1">Page1</Link>
<br />
<Link to="/page2">Page2</Link>
</div>
{/* ルーター設定を別ファイルに分離 */}
<Router />
</BrowserRouter>
);
}
export default App;
BrowserRouterでアプリ全体を囲むのがルーティングの起点のようです。
ナビゲーションには通常の<a>タグではなく、Linkコンポーネントを使います。
Linkはページリロードなしで画面を切り替えてくれるため、SPAとして動作するのだと理解しました。
2.2 ルーター設定(Router.jsx)
講座ではルーター設定をRouter.jsxに分離していました。
以下はその構成イメージです(v5の書き方):
import { Switch, Route } from "react-router-dom";
import { Home } from "../Home";
import { Page1 } from "../Page1";
import { Page2 } from "../Page2";
import { Page404 } from "../Page404";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
import { UrlParameter } from "../UrlParameter";
export const Router = () => {
return (
// Switchで囲むことで、上から順に最初にマッチしたRouteだけ表示する
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page1">
{/* ネストしたRouteもSwitchで囲む */}
<Switch>
<Route exact path="/page1" component={Page1} />
<Route path="/page1/detailA" component={Page1DetailA} />
<Route path="/page1/detailB" component={Page1DetailB} />
</Switch>
</Route>
<Route path="/page2">
<Switch>
<Route exact path="/page2" component={Page2} />
{/* :idの部分がURLパラメーター */}
<Route path="/page2/:id" component={UrlParameter} />
</Switch>
</Route>
{/* pathなしはどのURLにもマッチする→404ページに使う */}
<Route component={Page404} />
</Switch>
);
};
v5ではSwitchとRouteの組み合わせでルーティングを定義するみたいです。
exactを付けないと/page1のルートが/page1/detailAにもマッチしてしまうため、完全一致させたい場合はexactが必要になるようです。
v6との違い:Switch・Route・exact
v6ではSwitchが廃止されてRoutesに変わり、exactも不要になったようです。
またRouteのコンポーネント指定方法がcomponent={Home}ではなくelement={<Home />}に統一されたと理解しました。
// v6の書き方
import { Routes, Route } from "react-router-dom";
export const Router = () => {
return (
// Routesに変更(exactは不要)
<Routes>
<Route path="/" element={<Home />} />
<Route path="/page1" element={<Page1 />} />
<Route path="/page1/detailA" element={<Page1DetailA />} />
<Route path="/page1/detailB" element={<Page1DetailB />} />
<Route path="/page2" element={<Page2 />} />
{/* URLパラメーター */}
<Route path="/page2/:id" element={<UrlParameter />} />
{/* 404ページ */}
<Route path="*" element={<Page404 />} />
</Routes>
);
};
v6のRoutesは最も一致度の高いルートを自動で選んでくれるため、順番を気にしなくてよくなったようです。
3. useHistory・useNavigate(プログラム的なページ遷移)
ボタンクリックなどで画面遷移するには、Linkではなくhooksを使うみたいです。
Page1.jsx全体:
import { Link, useHistory } from "react-router-dom";
export const Page1 = () => {
const arr = [...Array(100).keys()];
// useHistoryでhistoryオブジェクトを取得する
const history = useHistory();
console.log(history);
// pushメソッドでページ遷移する
const onClickDetailA = () => history.push("/page1/detailA");
return (
<div>
<h1>Page1ページです</h1>
{/* stateとしてarrを渡す */}
<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
<br />
<Link to="/page1/detailB">DetailB</Link>
<br />
<button onClick={onClickDetailA}>DetailA</button>
</div>
);
};
useHistoryでhistoryオブジェクトを取得して、history.push()でページ遷移します。
またLinkにto={{ pathname: "...", state: arr }}と書くことで、遷移先のページにデータを渡せるみたいです。
Page1DetailA.jsx全体:
import { useLocation, useHistory } from "react-router-dom";
export const Page1DetailA = () => {
// useLocationでlocationオブジェクトを取得し、stateを分割代入
const { state } = useLocation();
console.log(state);
const history = useHistory();
// goBackで1つ前のページに戻る
const onClickBack = () => history.goBack();
return (
<div>
<h1>Page1DetailAページです</h1>
<button onClick={onClickBack}>戻る</button>
</div>
);
};
渡されたstateはuseLocationのstateで受け取れるみたいです。
前の画面に戻るにはhistory.goBack()を使います。
v6との違い:useHistory → useNavigate
v6ではuseHistoryが廃止されてuseNavigateに変わっています。
// v5
import { useHistory } from "react-router-dom";
const history = useHistory();
// pushで遷移
history.push("/page1/detailA");
// 戻る
history.goBack();
// v6
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
// そのまま遷移先を渡す
navigate("/page1/detailA");
// 数値で履歴を移動する(-1が1つ前)
navigate(-1);
v6ではひとつのnavigate関数がすべてのナビゲーションを担うシンプルな設計になったのようです。
またLinkのstateの書き方もv6で変わっています。
// v5
<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
// v6(stateを独立したpropに分離)
<Link to="/page1/detailA" state={arr}>DetailA</Link>
v6でもstateの受け取り方(useLocationのstate)は変わらないみたいです。
4. URLパラメーター・クエリパラメーター
URLからパラメーターを取得する方法を学びました。
Page2.jsx全体:
import { Link } from "react-router-dom";
export const Page2 = () => {
return (
<div>
<h1>Page2ページです</h1>
{/* URLパラメーター付きのLink */}
<Link to="/page2/999">URL Parameter</Link>
<br />
{/* クエリパラメーター付きのLink */}
<Link to="/page2/999?name=hogehoge">Query Parameter</Link>
</div>
);
};
UrlParameter.jsx全体:
import { useParams, useLocation } from "react-router-dom";
export const UrlParameter = () => {
// useParamsでURLパラメーターを取得する
const { id } = useParams();
// useLocationでsearchを取得する
const { search } = useLocation();
// URLSearchParamsでクエリ文字列をパースする
const query = new URLSearchParams(search);
return (
<div>
<h1>UrlParameterページです</h1>
<p>パラメーターは{id}です</p>
<p>クエリパラメーターは{query.get("name")}です</p>
</div>
);
};
useParamsはパス(/page2/:id)の:id部分を取得できます。
クエリパラメーター(?name=hogehoge)はJavaScript標準のURLSearchParamsを使ってパースするみたいです。
useLocationで取れるsearchが?name=hogehogeの文字列で、そこにURLSearchParamsを渡して.get("name")で値を取り出せます。
useParamsとuseLocationの使い方はv5・v6でほぼ変わらないようです。
5. 404ページ
どのルートにも一致しないURLへのフォールバック処理です。
Page404.jsx全体:
import { Link } from "react-router-dom";
export const Page404 = () => {
return (
<div>
<h1>ページが見つかりません</h1>
<Link to="/">トップに戻る</Link>
</div>
);
};
v5ではpathなしのRouteを最後に置くと、どのURLにもマッチしなかった場合のフォールバックになります。
v6ではpath="*"を使うように変わっています。
// v5
<Route component={Page404} />
// v6
<Route path="*" element={<Page404 />} />
まとめ
今回はReact Router v5を使ったSPAのルーティングを学び、v6との主な違いも合わせて整理しました。
v5→v6でAPIの書き方がシンプルに刷新されているため、両方を比較しながら学ぶのが理解しやすいと感じました。
今回の気づき
一番大きな気づきは、v6でexactが不要になり、SwitchがRoutesに変わった点です。
v5では順番やexactの有無でバグが生まれやすかったのが、v6では最も一致度の高いルートを自動選択してくれるためシンプルになったのようです。
またuseHistoryがuseNavigateに統一されたことで、1つの関数でpush・goBack・goForwardがすべて賄えるのも覚えやすいと理解しました。
ハマりやすいポイント
- v5で
exactを付け忘れると/page1/detailAにアクセスしても/page1のコンポーネントが表示されてしまう -
Linkのstateの書き方がv5とv6で異なり(オブジェクト内 vs 独立prop)、バージョンを混同しやすい - クエリパラメーターは
useParamsでは取れないためuseLocationのsearchとURLSearchParamsを使う必要がある