なぜこの記事を書こうと思ったのか。
Reactの開発経験が1年半ほどで、Next.jsの学習を始めたのですが、
個人的に現場の中で、ReactRouterでのパスネスト、動的ルーティングへの対応をしたことがありませんでした。
自習の中でAppRouterのディレクトリ構成の飲み込みが悪かった点などもあったので、一度React環境で学習しなおそうと思い今回の記事を書きました。
ごく一般的な書き方は把握している
学習を始めて以降今までですが、以下のような簡単なReactRouterの設定は使用してました。
遷移先のパスを並べる -> 呼び出されるページコンポーネントを並べる。と言った具合の至ってシンプルな実装。
※サンプル程度なので、App.tsx内に書いてます
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Top />} />
<Route path="/detail" element={<DetailList />} />
</Routes>
</BrowserRouter>
);
}
export default App;
何がわからなかったのか
動的ルーティングの使い所、Routeのネスト構造と書き方です。
一人称実装が経験主体のためこの手法を採用するにもあまり機会がなく、設計の際にも考慮できなかったなぁという点が若干今思うと心苦しいような、そんな気分です。
ただ、実際に調べてみると実装自体はそこまで難しくない(設計などの壁打ちは必要そうですが)という印象でした。
サンプル作ってやってみる
トップページは遷移先としての子要素無し。
その後にdetailListとパス名が書かれているタグが、ページの「子」として一覧に書かれています。書き方のルールとしては、
- 親Routeで子Routeをラップする。
- 子Routeのパス名は"/"が不要で遷移先パス名を記入する
と言ったところかなと思います。
ルーティングが記載されたApp.tsx
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Top />} /> /* 👈 子を持たないトップページ */
<Route path="/detailList" element={<DetailList />}> /* 👈 子を持つ遷移先 */
<Route path="name" element={<DetailListName />} />
<Route path="age" element={<DetailListAge />} />
<Route path="from" element={<DetailListFrom />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
トップページ(特に子要素を持たない) -> パス 「"/"」
import React, { FC, } from "react";
import { Link } from "react-router-dom";
const Top: FC = () => {
return (
<div>
<h1>これはトップページです</h1>
<Link to={"/detailList"}>リストに行きます</Link>
<div></div>
</div>
);
};
export default Top;
トップページから、「リストに行きます」ボタンをクリックして、ネストRouteの親である「"/detailList"」に遷移する。
以下のページが出力されます。
const DetailList = () => {
return (
<div>
<p>何をしりたいのか?</p>
<ul>
<li>
<Link to={"/"}>トップページに戻る</Link>
</li>
<li>
<Link to={"name"}>名前</Link>
</li>
<li>
<Link to={"age"}>年齢</Link>
</li>
<li>
<Link to={"from"}>出身</Link>
</li>
</ul>
<Outlet />
</div>
);
};
export default DetailList;
先ほど記入したRoute群です。
<Route path="/detailList" element={<DetailList />}> /* 👈 子を持つ遷移先 */
<Route path="name" element={<DetailListName />} />
<Route path="age" element={<DetailListAge />} />
<Route path="from" element={<DetailListFrom />} />
</Route>
子Routeが示すelementはいずれも以下のような簡単な文字列を返すTSXエレメントとしました。
const DetailListName = () => {
return (
<div>名前はタイソンです</div>
)
}
export default DetailListName
const DetailListAge = () => {
return (
<div>年齢は40歳手前です</div>
)
}
export default DetailListAge
const DetailListFrom = () => {
return (
<div>出身はじゃぱんです</div>
)
}
export default DetailListFrom
「"/detailList"」内で、Linkの内容として「名前」をクリックするとどうなるのか?
画面上には以下のように出力されます。
パスとしては、「"/detailList/name"」と表示されているため、無事に遷移構造が完成しています。
このまま別の、「年齢」と書かれているLinkをクリックすると以下のように切り替わります。
この時点でのパスは「"/detailList/age"」となっており、名前は表示されておらず、年齢に関することに切り替わっています。こちらも無事に遷移構造が完成しています。
子Routeのページを表示させるためには「Outlet」が必要です!!
以下の「👈」で書いてある「Outlet」タグを書き忘れると、ルーティングとしては成功しますが肝心の「子Route」の内容が出力されない状態になってしまいますので注意してください。
import { Link, Outlet } from "react-router-dom";
const DetailList = () => {
return (
<div>
<p>何をしりたいのか?</p>
<ul>
<li>
<Link to={"/"}>トップページに戻る</Link>
</li>
<li>
<Link to={"name"}>名前</Link>
</li>
<li>
<Link to={"age"}>年齢</Link>
</li>
<li>
<Link to={"from"}>出身</Link>
</li>
</ul>
<Outlet /> 👈 これを忘れないこと!!!!!
</div>
);
};
このアウトレット運用が考慮できると設計時により強そう。state管理で表示非表示を切り替えていたオーバーレイウインドウの、表示非表示の切り替えも、outlet管理でできるので、設計の幅が広がったなあという印象でした。
単純なネスト構造ができたので、+して動的ルーティングの設定を組み込んでみる。
先ほどの子Routeの書き方と違って、pathのprefixに「:」を付与することで、動的パスとして「キー名」を与えることができます。
import { BrowserRouter, Route, Routes } from "react-router-dom";
import DetailList from "./Pages/DetailList";
import Top from "./Pages/Top";
import DetailListName from "./Pages/DetailListName";
import DetailListAge from "./Pages/DetailListAge";
import DetailListFrom from "./Pages/DetailListFrom";
import DetailListMyId from "./Pages/DetailListMyId";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Top />} />
<Route path="/detailList" element={<DetailList />}>
<Route path="name" element={<DetailListName />} />
<Route path="age" element={<DetailListAge />} />
<Route path="from" element={<DetailListFrom />} />
{/* ↓これが動的ルーティングパスの記入方法 */}
<Route path=":id" element={<DetailListMyId />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
先ほどのdetailListに、idの入力欄と遷移関数を記入しました。
動的ルーティングは、遷移先の入力をする際にuseNavigateを使用してpathを生成する必要があります。
import React, { ChangeEvent, useState } from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";
const DetailList = () => {
const navigate = useNavigate(); // ページ遷移用のフック
// 追加された内容
const [name, setName] = useState("");
const onChangeName = (e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
const goMyId = () => {
if (name.trim()) {
navigate(`/detailList/${name}`);
}
};
return (
<div>
<p>何をしりたいのか?</p>
<ul>
<li>
<Link to={"/"}>トップページに戻る</Link>
</li>
<li>
<Link to={"name"}>名前</Link>
</li>
<li>
<Link to={"age"}>年齢</Link>
</li>
<li>
<Link to={"from"}>出身</Link>
</li>
// 追加された入力欄
<li>
<input type="text" value={name} onChange={onChangeName} />
<button onClick={goMyId}>入力したidを動的ルーティングで表示</button>
</li>
</ul>
<Outlet />
</div>
);
};
export default DetailList;
入力欄にidを入力してボタンをクリックすると以下のように画面出力されます。
pathとしては「/detailList/nuhaha2023」となっていて、成功になります。
表示されている動的子Route
useParamsの分割代入で、「動的パスとして「キー名」を指定することが出来るので、これで動的ルーティングでの値の取得ができました。
import React from "react";
import { useParams } from "react-router-dom";
const DetailListMyId = () => {
const { id } = useParams();
return (
<div>
<div>動的ルーティングで取得した値。君のidは{id}だね!</div>
</div>
);
};
export default DetailListMyId;
補足
動的ルーティング (:id) を使うべきケース
データの取得に必須の識別子 (id, slug など) を URL に含めたいとき
例: /detailList/123 → 「123 の詳細ページ」
URL が 「リソースを一意に特定できる」 構造になるので、リロードしても問題なし
クエリパラメータ (?key=value) を使うべきケース
検索条件やフィルタリング情報のように「オプション」の値を持たせたいとき
例: /products?category=books&sort=price
直接編集されてもシステムに影響がない(例えば /products にアクセスしてもページは表示できる)
「このページの表示設定を変更するだけ」の情報ならクエリパラメータが適している
最後に
まだ学習始めたばかりの頃によくわからないなぁとなり、その後なんとなく食わず嫌いしてしまっていた内容でしたがこの記事の執筆を持って理解が進み、割と今後のパス周りの設計や画面としての情報保持観点なんかの考え方の幅がかなり広がったなぁという印象でした。ただ、もう少し早く知っておくべきだったかもしれません。
今後の実装を行う際に活用できますように....