mcpField
T
tanstack-router

tanstack-router

github.com/mindrally开发30077 次安装4 周前更新
代码仓库

关于 tanstack-router

用于 TanStack Router 的开发技能,适合 React 开发者构建类型安全的路由。内容涵盖基于文件的路由、搜索参数验证、loader、访问守卫和类型化导航。

主要功能

  1. 01类型安全的路由与导航
  2. 02基于文件的路由约定
  3. 03验证搜索参数
  4. 04通过 loader 加载数据
  5. 05使用 beforeLoad 检查访问权限

适用场景

  1. 01为 React 应用添加类型化路由
  2. 02验证查询参数
  3. 03在加载数据前保护路由

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mindrally/skills --skill tanstack-router

安装后在 Claude Code 中输入/tanstack-router确认已加载。

详细介绍

TanStack Router

TanStack Router 是一个完全类型安全的 React 路由器,它将路由、加载器、参数和搜索参数视为一等类型化数据,而不是无类型字符串。

添加新路由的工作流

  1. 创建路由文件 — 按照基于文件的路由约定(见下文),在 src/routes/ 下添加文件。
  2. 定义路由 — 从 createFileRoute('/path')({...}) 导出 Route,并提供 component,还可选提供 loader、validateSearch、beforeLoad、errorComponent 和 pendingComponent。
  3. 验证搜索参数 — 如果路由会读取查询参数,请定义 Zod 架构并将其传入 validateSearch。
  4. 加载数据 — 在 loader 中获取数据,而不是在组件的 useEffect 中获取;需要缓存时,通过 queryClient.ensureQueryData 集成 TanStack Query。
  5. 设置访问保护 — 添加 beforeLoad 检查(例如身份验证);当前置条件不满足时,执行 throw redirect({ to: '/login' })。
  6. 链接到路由 — 使用 <Link to="/path" params={...} search={...}> 导航,以便编译器在每个调用位置验证参数和搜索参数。
  7. 重新生成路由树 — 运行或构建应用之前,确保已重新生成 routeTree.gen.ts(Vite 插件的开发服务器/构建过程会自动执行)。

核心原则

  • TanStack Router 具有 100% 的类型安全性 — 使用 TypeScript 泛型处理参数、搜索参数和加载器数据,而不是手动类型转换。
  • 为提高可扩展性,优先使用 @tanstack/router-vite-plugin(或 @tanstack/router-plugin/vite)进行基于文件的路由,而不是手动构建路由树。
  • 始终使用 createFileRoute(叶路由/嵌套路由)或 createRootRoute/createRootRouteWithContext(根路由)定义路由。
  • 路由数据应在 loader 函数中加载,而不是在组件的 useEffect 中加载 — 这样才能支持预加载、并行加载以及加载中/错误状态。
  • 搜索参数是一等状态 — 始终使用 Zod(或其他标准架构验证器)定义其架构,以便在每次读取时进行类型检查和验证。

基于文件的路由约定

src/routes/
  __root.tsx          ← Root layout
  index.tsx           ← / route
  posts/
    index.tsx         ← /posts
    $postId.tsx       ← /posts/:postId (dynamic segment)
    _layout.tsx       ← Layout route (no path segment)
  _auth/               ← Pathless auth layout group
    dashboard.tsx
  • 片段开头的下划线(_layout、_auth)会创建无路径布局路由,专用于分组/共享 UI。
  • $ 前缀($postId)表示动态路径片段,与 Route.useParams() 匹配。
  • 文件夹中的 index.tsx 与该文件夹自身的路径匹配,不会增加额外片段。

路由定义

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => fetchPost(params.postId),
  component: PostComponent,
  errorComponent: ({ error }) => <ErrorBanner message={error.message} />,
  pendingComponent: () => <PostSkeleton />,
})

function PostComponent() {
  const post = Route.useLoaderData()     // type-safe
  const { postId } = Route.useParams()   // type-safe
  return <div>{post.title}</div>
}
  • errorComponent 会在加载器抛出错误时渲染;pendingComponent 会在加载器执行期间渲染(受 defaultPendingMs 影响)。
  • 加载器的返回值和抛出的错误都具有完整类型,并分别传递给 useLoaderData() 和 errorComponent。

类型安全的搜索参数

  • 始终使用 Zod 和 validateSearch 定义搜索参数。
  • 使用 Route.useSearch() 访问它们 — 切勿直接读取 window.location.search 或 URLSearchParams,否则会绕过类型安全和验证。
const searchSchema = z.object({
  page: z.number().int().min(1).default(1),
  q: z.string().optional(),
})

export const Route = createFileRoute('/search')({
  validateSearch: searchSchema,
  component: SearchPage,
})

function SearchPage() {
  const { page, q } = Route.useSearch()
  const navigate = Route.useNavigate()

  return (
    <button onClick={() => navigate({ search: (prev) => ({ ...prev, page: page + 1 }) })}>
      Next page
    </button>
  )
}

导航

  • 内部导航使用 <Link> — 切勿使用原生 <a href>,否则会触发整页重新加载并丢失客户端路由状态。
  • 始终传入类型化的 params 和 search;TypeScript 编译器会在构建时捕获缺失或类型错误的路由参数。
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/search" search={{ page: 1, q: 'react' }}>Search</Link>

加载器 + TanStack Query 集成

  • 将 QueryClient 放入路由器上下文,以便加载器通过 TanStack Query 预取和缓存数据,而不是重复编写获取数据的逻辑。
  • 在加载器中使用 ensureQueryData(而不是 fetchQuery),这样会复用已缓存的查询,而不是重新获取。
export const Route = createFileRoute('/posts')({
  loader: ({ context: { queryClient } }) =>
    queryClient.ensureQueryData(postsQueryOptions()),
  component: PostsPage,
})

function PostsPage() {
  const { data: posts } = useSuspenseQuery(postsQueryOptions())
  return <PostList posts={posts} />
}

用于依赖注入的路由器上下文

// __root.tsx
interface RouterContext {
  queryClient: QueryClient
  auth: AuthState
}

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootLayout,
})

// main.tsx
const router = createRouter({ routeTree, context: { queryClient, auth } })
  • 路由器上下文通过 context 参数传递给每个路由的 loader 和 beforeLoad,让每个路由都能以类型安全的方式访问共享依赖,而无需逐层传递属性或使用全局单例。

身份验证守卫

export const Route = createFileRoute('/_auth/dashboard')({
  beforeLoad: ({ context }) => {
    if (!context.auth.isAuthenticated) throw redirect({ to: '/login' })
  },
  component: Dashboard,
})
  • beforeLoad 会在 loader 运行前、组件渲染前执行,因此适合用于身份验证检查、功能标志门控和重定向。
  • 优先使用 throw redirect(...),而不是在组件中进行命令式导航——它在 SSR、预加载和客户端导航中都能正常工作。

性能

  • 在路由器上设置 defaultPreload: 'intent',让链接在悬停或获得焦点时预取其路由数据,使导航体验更加即时。
  • 对于大型应用,使用 React.lazy(或通过 .lazy() 路由文件使用路由器内置的代码拆分)来拆分路由组件代码。
  • 安装 @tanstack/router-devtools,并在开发环境中渲染 <TanStackRouterDevtools />,以检查路由匹配、待处理状态和缓存。

常见错误

  • 在路由组件中使用 useEffect 获取数据,而不是使用 loader,这会失去预加载能力,并造成先渲染、后获取的瀑布流。
  • 读取原始的 window.location.search,而不是使用 Route.useSearch(),这会绕过搜索参数架构和类型安全。
  • 只在组件中执行身份验证检查(使用 useEffect 重定向),而不是使用 beforeLoad,导致重定向前受保护内容短暂闪现。
  • 忘记在 <Link> 调用处添加新的路由参数或搜索字段——让编译器来发现问题,而不是手动测试。

评论

支持 Markdown