先週のブログの続きです。昔はReact Routerを使っていたのですが、最近はNext.jsばかり使っていて、久々にReact Routerにふれたので、React Router v7のルーティング機能を調べてみました。
Bing Image Creatorが生成した画像を使っています
今回のアプリ
以下の画像のように、いつものジャンケンアプリにタブを追加し、/scores
をアクセスすると対戦結果、/status
をアクセスすると対戦成績を表示するようにしました(/
にアクセスした場合は/scores
にリダイレクトします)。
React Router v7のルーティング
React Router v7のルーティングには、3つの方式があるようです。
- Configuring Routes :
create-react-router
コマンドで作られるアプリのデフォルトでroutes.ts
ファイルに定義します - Component Routes : v6以前からある
<Route path="/scores" element={<Scores />} />
のようなルーティングようにコンポーネントも使えます。ただし、データローディング等はサポートしてないのでRemix的な使い方は出来ません - File Route Conventions : Next.jsのPages Routerのようなファイル名ベースのルーティングです。
今回はConfiguring RoutesとFile Route Conventionsを試してみました。
Configuring Routes
Configuring Routesがv7の標準なのかな
app/routes.ts
ここにルーティング定義を書きます。見てわかるように
/
: routes/home.tsxコンポーネントを実行/scores
: routes/scores.tsxコンポーネントを実行/status
: routes/status.tsxコンポーネントを実行
パス(URL)がネストする場合はドキュメントにあるように、route
の第3引数、またはprefix
の第2引数に子要素の定義を書けば良いようです。
import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("scores", "routes/scores.tsx"),
route("status", "routes/status.tsx"),
] satisfies RouteConfig;
app/routes/scores.tsx
先週のhome.tsxのうち、meta
、loader
、 action
と Jyanken
コンポーネントの呼出しだけを取り出したものです。
meta
、loader
、action
はルーティング設定にあるコンポーネント内にある必要があるようです。- ①でインポートしているファイルは
.react-router/types/app/routes/+types/scores.ts
にあり、開発環境により自動生成されます
import type { Route } from "./+types/scores"; // ← ①
import { doPon, getScores } from "~/libs/JyankenActions";
import Jyanken from "~/components/Jyanken";
export function meta({}: Route.MetaArgs) {
return [
{ title: "ジャンケン React Router App" },
{ name: "description", content: "React Router v7を使ったジャンケン・アプリ" },
];
}
export async function loader() {
return await getScores();
}
export async function action({request}: Route.ActionArgs) {
const formData = await request.formData();
await doPon(formData.get("human") as string);
}
export default function Home({loaderData}: Route.ComponentProps) {
return (
<Jyanken tabIx={0} scores={loaderData}/>
);
}
app/routes/scores.tsx
app/routes/scores.tsx
とほぼ同じです。なんとか共通にしたいですが、わかりませんでした。
import type { Route } from "./+types/status";
import { doPon, getScores } from "~/libs/JyankenActions";
import Jyanken from "~/components/Jyanken";
export function meta({}: Route.MetaArgs) {
return [
{ title: "ジャンケン React Router App" },
{ name: "description", content: "React Router v7を使ったジャンケン・アプリ" },
];
}
export async function loader() {
return await getScores();
}
export async function action({request}: Route.ActionArgs) {
const formData = await request.formData();
await doPon(formData.get("human") as string);
}
export default function Home({loaderData}: Route.ComponentProps) {
return (
<Jyanken tabIx={1} scores={loaderData}/>
);
}
app/routes/home.tsx
/
でアクセスされた際に /scores
にリダイレクトしています。
開発環境ではURLは切り替わっていますが、404 The requested page could not be found.
になってしまいますが、ビルドされたコードでは正しくリダイレクトされます。(Viteの問題かな ?)
import { useEffect } from "react";
import { useNavigate } from "react-router";
export default function Home() {
const navigate = useNavigate();
useEffect(() => {
navigate("/scores");
}, []);
return null;
}
app/components/Jyanken.tsx
先週のhome.tsxのHomeコンポーネント部分です。
- ① タブをクリックしたさいのページ遷移を行うコードです
import { calcStatus, ScoreType } from "~/libs/JyankenTypes";
import JyankenBox from "~/components/JyankenBox";
import ScoreBox from "~/components/ScoreBox";
import StatusBox from "~/components/StatusBox";
import Tabs from "~/components/Tabs";
import { useNavigate } from "react-router";
type JyankeProps = {
tabIx: number,
scores: ScoreType[]
}
export default function Jyanken({tabIx, scores}: JyankeProps) {
const navigate = useNavigate();
const redirect = (ix: number) => {
navigate(["/scores", "/status"][ix]); // ← ①
}
return (
<div className="md:ml-8">
<h1 className="my-4 ml-4 text-3xl font-bold">じゃんけん ポン!</h1>
<div className="p-3 md:p-6 bg-white md:w-3/5">
<JyankenBox />
<Tabs titles={["対戦結果", "対戦成績"]} tabIndex={tabIx}
setTabIndex={ix => redirect(ix)} />
{tabIx == 0 && <ScoreBox scores={scores} />}
{tabIx == 1 && <StatusBox status={calcStatus(scores)} />}
</div>
</div>
);
}
先週のブログ と同じコードは省きました。
File Route Conventions
ファイル・ディレクトリー名によるルーティングです。
- まず
@react-router/fs-routes
npmをインストールします - ルーティング定義
app/routes.ts
を以下のように書き換えます
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;
/
の対応するファイルhome.tsx
を_index.tsx
に変更します
以上で完了です、Next.jsユーザーにはわりやすいルーティングですね。ただし、URLのネストはディレクトリーではなくファイル名中の.
が使われるので注意が必要です。
まとめ
React Routerはバージョンアップの度にルート定義方法がかわってきましたが、やはりv7でも変わっていました。
ただしv6と同じComponent Routesをサポートしているのでv6ユーザーにはありがたいですね。
比較的シンプルなルート構造なら File Route Conventions を使い、レイアウトファイルの対応付けや、URL中のパラメーターなどが複雑な場合は Configuring Routes を使うと良さそうですね。