@tanstack/react-router で virtual routeのパスを型安全に取得する
以下のようなケース
link.to
は存在しないルートでも指定できてしまう
const footerLinks = [
{ label: "利用規約", to: "/terms" },
{ label: "プライバシーポリシー", to: "/privacy-policy" },
{ label: "お問い合わせ", to: "/contacts" },
];
export const Footer = () => {
return (
<footer className={styles.footer}>
<div className={styles.links}>
{footerLinks.map((link) => (
<Link key={link.label} to={link.to}>
{link.label}
</Link>
))}
</div>
</footer>
);
};
遷移先取得の推奨方法は以下の通り
Routeオブジェクトには to
プロパティが生えているからなるべくそっちを使った方がメンテナブルとのこと
🧠 Every route object has a to property, which can be used as the to for any navigation or route matching API. Where possible, this will allow you to avoid plain strings and use type-safe route references instead:
function Comp() { return <Link to={aboutRoute.to}>About</Link> }
import { Route as termsRoute } from '@/routes/terms/route'
import { Route as privacyPolicyRoute } from '@/routes/privacy-policy/route'
import { Route as contactsRoute } from '@/routes/contacts/route'
const footerLinks = [
{ label: "利用規約", to: termsRoute.to },
{ label: "プライバシーポリシー", to: privacyPolicyRoute.to },
{ label: "お問い合わせ", to: contactsRoute.to },
];
export const Footer = () => {
return // 同じ
};
通常のルートならいいのだけどCodeSplittingのためにすべてLazyRouteにしているから困った
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/xxx')({
component: () => <div>Hello /xxx!</div>
})
import { Route as termsRoute } from '@/routes/terms/route.lazy'
import { Route as privacyPolicyRoute } from '@/routes/privacy-policy/route.lazy'
import { Route as contactsRoute } from '@/routes/contacts/route.lazy'
// LazyRouteオブジェクトには to プロパティがないのでエラーになる
const footerLinks = [
{ label: "利用規約", to: xxx },
{ label: "プライバシーポリシー", to: privacyPolicyRoute.to },
{ label: "お問い合わせ", to: contactsRoute.to },
];
以下の方法であればルートオブジェクトから to
を取得しつつCodeSplitできるが、LazyRouteのアンカーはrouteTreeに自動生成されるためわざわざ分割しないでねとドキュメントにある(非推奨)
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
// 空のルートオブジェクトを作る
})
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/posts')({
component: Posts,
})
function Posts() {
// 本体をLazyRouteとして定義する
}
You might run into a situation where you end up splitting out everything from a route file, leaving it empty! In this case, simply delete the route file entirely! A virtual route will automatically be generated for you to serve as an anchor for your code split files. This virtual route will live directly in the generated route tree file.
自動生成されるrouteTree.gen.ts
を見てみると
LazyRouteのアンカーはrouteTreeに生成されているが、exportされていない。これが取得できれば to
プロパティが使えるんだけどなぁ。
// Create Virtual Routes
const TermsRouteLazyImport = createFileRoute("/terms")();
const PrivacyPolicyRouteLazyImport = createFileRoute("/privacy-policy")();
const ContactsRouteLazyImport = createFileRoute("/contacts")();
これらのVirtualRouteオブジェクトは、routeTree.gen.ts
内ではLazyImportのアタッチと、型定義に使用されている(あっ、型定義・・・)
// Populate the FileRoutesByPath interface
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
"/contacts": {
preLoaderRoute: typeof ContactsRouteLazyImport;
parentRoute: typeof rootRoute;
};
"/privacy-policy": {
preLoaderRoute: typeof PrivacyPolicyRouteLazyImport;
parentRoute: typeof rootRoute;
};
"/terms": {
preLoaderRoute: typeof TermsRouteLazyImport;
parentRoute: typeof rootRoute;
};
}
}
ということは FileRoutesByPath
型を使えばいいだけだった・・・
import type { FileRoutesByPath } from "@tanstack/react-router";
type FooterLink = {
label: string;
to: keyof FileRoutesByPath; // これで存在しない型は指定できなくなる
};
const footerLinks: FooterLink[] = [
{ label: "利用規約", to: "/terms" },
{ label: "プライバシーポリシー", to: "/privacy-policy" },
{ label: "お問い合わせ", to: "/contacts" },
];
export const Footer = () => {
return // 型安全・・・幸せ・・・
};