EY-Office ブログ

あらためてReact Router v7のルーティングを調べてみた

先週のブログの続きです。昔はReact Routerを使っていたのですが、最近はNext.jsばかり使っていて、久々にReact Routerにふれたので、React Router v7のルーティング機能を調べてみました。

React Router v7 Bing Image Creatorが生成した画像を使っています

今回のアプリ

以下の画像のように、いつものジャンケンアプリにタブを追加し、/scoresをアクセスすると対戦結果、/statusをアクセスすると対戦成績を表示するようにしました(/にアクセスした場合は/scoresにリダイレクトします)。

React Router v7のルーティング

React Router v7のルーティングには、3つの方式があるようです。

  1. Configuring Routescreate-react-routerコマンドで作られるアプリのデフォルトでroutes.tsファイルに定義します
  2. Component Routes : v6以前からある<Route path="/scores" element={<Scores />} />のようなルーティングようにコンポーネントも使えます。ただし、データローディング等はサポートしてないのでRemix的な使い方は出来ません
  3. 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のうち、metaloaderactionJyankenコンポーネントの呼出しだけを取り出したものです。

  • metaloaderactionはルーティング設定にあるコンポーネント内にある必要があるようです。
  • ①でインポートしているファイルは .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

ファイル・ディレクトリー名によるルーティングです。

  1. まず @react-router/fs-routes npmをインストールします
  2. ルーティング定義 app/routes.ts を以下のように書き換えます
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default flatRoutes() satisfies RouteConfig;
  1. /の対応するファイル home.tsx_index.tsx に変更します

以上で完了です、Next.jsユーザーにはわりやすいルーティングですね。ただし、URLのネストはディレクトリーではなくファイル名中の.が使われるので注意が必要です。

まとめ

React Routerはバージョンアップの度にルート定義方法がかわってきましたが、やはりv7でも変わっていました。
ただしv6と同じComponent Routesをサポートしているのでv6ユーザーにはありがたいですね。

比較的シンプルなルート構造なら File Route Conventions を使い、レイアウトファイルの対応付けや、URL中のパラメーターなどが複雑な場合は Configuring Routes を使うと良さそうですね。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ