Closed7

@tanstack/react-router で virtual routeのパスを型安全に取得する

ミラミラミラミラ

以下のようなケース
link.toは存在しないルートでも指定できてしまう

footer.tsx
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>
}

https://tanstack.com/router/latest/docs/framework/react/guide/navigation#tooptions-interface

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にしているから困った

routes/xxx/route.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/xxx')({
  component: () => <div>Hello /xxx!</div>
})
components/layout/footer.tsx
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に自動生成されるためわざわざ分割しないでねとドキュメントにある(非推奨)

route.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
  // 空のルートオブジェクトを作る
})
route.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/posts')({
  component: Posts,
})
function Posts() {
  // 本体をLazyRouteとして定義する
}
ミラミラミラミラ

自動生成されるrouteTree.gen.ts を見てみると

LazyRouteのアンカーはrouteTreeに生成されているが、exportされていない。これが取得できれば to プロパティが使えるんだけどなぁ。

routeTree.gen.ts
// Create Virtual Routes

const TermsRouteLazyImport = createFileRoute("/terms")();
const PrivacyPolicyRouteLazyImport = createFileRoute("/privacy-policy")();
const ContactsRouteLazyImport = createFileRoute("/contacts")();
ミラミラミラミラ

これらのVirtualRouteオブジェクトは、routeTree.gen.ts内ではLazyImportのアタッチと、型定義に使用されている(あっ、型定義・・・)

routeTree.gen.ts
// 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 型を使えばいいだけだった・・・

footer.tsx
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 // 型安全・・・幸せ・・・
};

このスクラップは2024/04/19にクローズされました