💡

React Router v7のtypegenを使い自動生成した型情報でパラメータに型を指定する

2024/10/20に公開

はじめに

2024/10/4にプレリリースされたReact Router v7では、Typesafetyの改善としてreact-router typegenというコマンドが追加されています。
このコマンドを実行する事で、各パスに紐づいているRouteコンポーネントの型が自動生成されます。
この記事では、生成されたtypesをアプリケーションで使用する為のセットアップと使い方を紹介します。

セットアップ

1. typegenの実行

早速、型ファイルを生成します。

bun react-router typegen

コマンドを実行すると型情報.react-routerディレクトリに出力されます。
例えば、routes/users.$user/route.tsxに対応するtypesは以下の様になります。Params型でパスパラメーターのidが宣言されています。

// React Router generated types for route:
// routes/users.$user/route.tsx

import type * as T from "react-router/types";

export type Params = {
	id: string;
};

type Route = typeof import("./route");

export type LoaderData = T.CreateLoaderData<Route>;
export type ActionData = T.CreateActionData<Route>;

export type LoaderArgs = T.CreateServerLoaderArgs<Params>;
export type ClientLoaderArgs = T.CreateClientLoaderArgs<Params, Route>;
export type ActionArgs = T.CreateServerActionArgs<Params>;
export type ClientActionArgs = T.CreateClientActionArgs<Params, Route>;

export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Params>;
export type ComponentProps = T.CreateComponentProps<
	Params,
	LoaderData,
	ActionData
>;
export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<
	Params,
	LoaderData,
	ActionData
>;

出力される型情報は以下の情報から型推論されています。

  • Params: ファイルベースのルーティングを含むroutes.tsからのパスパラメーター
  • LoaderData: Routeコンポーネント内のloader, clientLoader
  • ActionData : Routeコンポーネント内のaction, clientAction

2. tsconfig,jsonの更新

生成された型ファイルにアクセスするためにtsconfig,jsonを更新します。
.react-router/types配下のパスはapp配下のパスと同様になる為、Routeコンポーネントから./+types.routeの様にアクセスできる様になります。

{
+  "include": [".react-router/types/**/*"],
  "compilerOptions": {
+    "rootDirs": [".", "./.react-router/types"]
  }
}

また、pluginを指定する事でroutesが変更される度に型情報の自動生成が行えます。

{
  "include": [".react-router/types/**/*"],
  "compilerOptions": {
    "rootDirs": [".", "./.react-router/types"]
+   "plugins": [{ "name": "@react-router/dev" }]
  }
}

3. コンポーネントからアクセスする

以下の様にloaderRouteimportし、Route.LoaderArgsの様に型情報にアクセスできます。

import type * as Route from "./+types.route";

export function loader({ params }: Route.LoaderArgs) {}
export default function Component({ loaderData }: Route.ComponentProps) {}

useParamsなどloader以外から参照する場合も同様です。

import type * as Route from "./+types.route";

export default function User() {
    const params = useParams() as Route.Params;
    const userId = params.id
}

変わったこと

useParamsの返り値の型はstring | undefinedなので、これまでは以下のいずれかでパラメーターをstringとして扱う必要がありました。

  1. Params型を明示的に定義する
  2. params.id!としてundefinedの場合にエラーにする
  3. userIdが存在する事を保証する
import { useParams } from "react-router";

// 1. `Params`型を明示的に定義するパターン
type Params = {
    id: string;
};

export default function User() {
    const params = useParams() as Params;
    // 2. `params.id!`として`undefined`の場合にエラーにするパターン
    const userId = params.id!;

    return (
        // 3. `userId`が存在する事を保証するパターン
        {userId && <UserEdit userId={userId} />}
    )
}

これからは、自動生成された型情報にアクセスすることで以下のように記述することができます。

import type * as Route from "./+types.route";

export default function User() {
    const params = useParams() as Route.Params;
    const userId = params.id

    return (
        <UserEdit userId={userId} />
    )
}

今後の補足情報

将来的には以下の要素なども追加される様です。

  • meta
  • links
  • headers
  • shouldRevalidate

また、Linkコンポーネントでもtype safeなparams指定が行える様に計画しているとの事です。

終わりに

Remix v2からの移行でReact Router v7を使っているのですが、こうしたTypesafetyの改善は便利でワクワクしますね。今後のLinkコンポーネントの対応など、さらなる開発体験の向上が楽しみです。

環境

$ bun envinfo --npmPackages @react-router/node,@react-router/fs-routes,react-router,react-router-dom,@react-router/dev

  npmPackages:
    @react-router/dev: ^7.0.0-pre.1 => 7.0.0-pre.1 
    @react-router/fs-routes: ^7.0.0-pre.1 => 7.0.0-pre.1 
    @react-router/node: ^7.0.0-pre.1 => 7.0.0-pre.1 
    react-router: ^7.0.0-pre.1 => 7.0.0-pre.1 
    react-router-dom: ^7.0.0-pre.1 => 7.0.0-pre.1 

参考

Discussion