Zenn
Open10

React Route v7触ってみる

namidapoonamidapoo

ルーティング

ルートの設定

https://react-router-docs-ja.techtalk.jp/start/framework/routing#ルートの設定

ルートは app/routes.ts で設定します。各ルートには、URL に一致させるための URL パターンと、その動作を定義するルートモジュールへのファイルパスという、2 つの必須部分があります。

app/routes.ts
import {
 type RouteConfig,
 route,
} from "@react-router/dev/routes";

export default [
 route("some/path", "./some/file.tsx"),
 // pattern ^           ^ モジュールファイル
] satisfies RouteConfig;

とドキュメントには書いてあるけど構築されたプロジェクトのコード見てみると以下のようになってる

app/routes.ts
import { type RouteConfig, index } from "@react-router/dev/routes";

export default [index("routes/home.tsx")] satisfies RouteConfig;

routeの第一引数が/、つまりインデックスルートのときのヘルパー的な感じでindexが用意されてるだけっぽい

namidapoonamidapoo

ルートをネストする

https://react-router-docs-ja.techtalk.jp/start/framework/routing#ネストされたルート

app/routes.ts
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
	index("routes/home.tsx"),
	route("dashboard", "routes/dashboard.tsx", [
		// 子ルート
		index("routes/dashboard/home.tsx"),
		route("settings", "routes/dashboard/settings.tsx"),
	]),
] satisfies RouteConfig;
モジュールファイルの追加
app/routes/dashboard.tsx
import type { FC } from "react";
import { Outlet } from "react-router";

const Dashboard: FC = () => {
	return (
		<div className="flex h-screen items-center justify-center gap-4 text-2xl">
			dashboard
			<span>/</span>
			<Outlet />
		</div>
	);
};

export default Dashboard;
app/routes/dashboard/home.tsx
import type { FC } from "react";

const DashboardHome: FC = () => {
	return <div>home</div>;
};

export default DashboardHome;
app/routes/dashboard/settings.tsx
import type { FC } from "react";

const DashboardSettings: FC = () => {
	return <div>settings</div>;
};

export default DashboardSettings;
/dashboard /dashboard/settings
namidapoonamidapoo

ファイルベースルーティング

設定ではなく、ファイル命名規則でルートを定義したい場合は、@react-router/fs-routes パッケージで ファイルシステムルーティング規則 が提供されます。必要に応じて、さまざまなルーティング規則を組み合わせることもできます。

app/routes.ts
import {
  type RouteConfig,
  route,
} from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default [
  route("/", "./home.tsx"),

  ...(await flatRoutes()),
] satisfies RouteConfig;

とあるけどflatRoutes使うならapp/routes/_index.tsxが必須っぽい...?
命名規則でこれがルートルートになる、と思うから以下のようにすればいいのでは...?
その場合↑の例のroute("/", "./home.tsx")はなんなんだろう

app/routes.ts
import type { RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default [...(await flatRoutes())] satisfies RouteConfig;

flatRoutesの規則に載ってこないようなルートを手動で追加することもできる

app/routes.ts
import { type RouteConfig, route } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default [
	route("/test", "./test.tsx"),
	...(await flatRoutes()),
] satisfies RouteConfig;
namidapoonamidapoo

レイアウトルート

layout を使用すると、レイアウトルートは子に対して新しいネストを作成しますが、URL にセグメントを追加しません。ルートルートに似ていますが、任意のレベルで追加できます。

app/routes.ts
import {
	type RouteConfig,
	index,
	layout,
	prefix,
	route,
} from "@react-router/dev/routes";

export default [
	layout("./marketing/layout.tsx", [
		index("./marketing/home.tsx"),
		route("contact", "./marketing/contact.tsx"),
	]),
	...prefix("projects", [
		index("./projects/home.tsx"),
		layout("./projects/project-layout.tsx", [
			route(":pid", "./projects/project.tsx"),
			route(":pid/edit", "./projects/edit-project.tsx"),
		]),
	]),
] satisfies RouteConfig;

文字通りネスト構造は作れるけどURL構造には影響を与えない

namidapoonamidapoo

ルートプレフィックス

prefix を使用すると、親ルートファイルを導入せずに、ルートのセットにパスプレフィックスを追加できます。

app/routes.ts
import {
	type RouteConfig,
	index,
	layout,
	prefix,
	route,
} from "@react-router/dev/routes";

export default [
	layout("./marketing/layout.tsx", [
		index("./marketing/home.tsx"),
		route("contact", "./marketing/contact.tsx"),
	]),
	...prefix("projects", [
		index("./projects/home.tsx"),
		layout("./projects/project-layout.tsx", [
			route(":pid", "./projects/project.tsx"),
			route(":pid/edit", "./projects/edit-project.tsx"),
		]),
	]),
] satisfies RouteConfig;

こっちはlayoutとは逆でネストは作らないけどURL構造的なprefixを付与したグループを作れる

namidapoonamidapoo

動的セグメント

パスセグメントが : で始まる場合、それは「動的セグメント」になります。ルートが URL と一致すると、動的セグメントは URL から解析され、他のルーター API に params として提供されます。

app/routes.ts
route("teams/:teamId", "./team.tsx"),
app/team.tsx
import type { Route } from "./+types/team";

export async function loader({ params }: Route.LoaderArgs) {
  //                           ^? { teamId: string }
}

export default function Component({
  params,
}: Route.ComponentProps) {
  params.teamId;
  //        ^ string
}

試しにコンポーネントでpropsから取得してみた

ファイルベースルーティングの場合はteams.$teamId.tsxみたいな感じ?

namidapoonamidapoo

オプションのセグメント

セグメントの最後に ? を追加すると、ルートセグメントをオプションにすることができます。

app/routes.ts
route(":lang?/categories", "./categories.tsx"),

オプションの静的セグメントを含めることもできます。

app/routes.ts
route("users/:userId/edit?", "./user.tsx");

たしかにオプショナルにすると他のルーター API から params を参照したときにstring | undefinedのようなユニオンになっている

namidapoonamidapoo

スプラット

「キャッチオール」および「スター」セグメントとも呼ばれます。ルートパスパターンが /* で終わる場合、他の / 文字を含む、/ に続く任意の文字に一致します。

app/routes.ts
route("files/*", "./files.tsx"),
app/files.tsx
import type { FC } from "react";
import type { Route } from "./+types/files";

const Files: FC<Route.ComponentProps> = ({ params }) => {
        // params["*"] には、files/ の後の残りの URL が含まれます
	const { "*": splat } = params;
	
	return (
		<div className="flex h-screen items-center justify-center text-2xl">
			project {splat}
		</div>
	);
};

export default Files;

params["*"]の型的にはstringになるみたい

ログインするとコメントできます