TanStack Router 基础

要点

路由把位置变成可保存、可分享的状态。搜索参数来自文本,需要解析与校验,不能因 TypeScript 类型存在就信任地址栏输入。

路由树与文件结构

定义: TanStack Router 把 src/routes/ 目录结构 1:1 映射为一棵路由树,并由 Vite 插件生成 routeTree.gen.ts,让每个路由的 params、search、context 都有精确类型。

文件命名约定:

文件路由说明
__root.tsx根布局整个应用的壳,必须存在
index.tsx/(所在目录的索引)目录默认页
about.tsx/about普通静态路由
posts/$postId.tsx/posts/:postId$ 前缀 = 动态参数
posts/index.tsx/postsposts 目录索引
_layout.tsx不产生路径段下划线前缀 = 仅提供布局包裹

实践:搭建带导航的多页应用

步骤一:根布局 src/routes/__root.tsx

import { createRootRoute, Link, Outlet } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: RootLayout,
})

function RootLayout() {
  return (
    <div className="mx-auto max-w-2xl p-6">
      <nav className="mb-6 flex gap-4">
        {/* to 的取值有类型约束,拼错路径直接编译报错 */}
        <Link to="/" activeProps={{ className: 'font-bold' }}>
          首页
        </Link>
        <Link to="/posts" activeProps={{ className: 'font-bold' }}>
          文章列表
        </Link>
      </nav>
      {/* 子路由渲染出口 */}
      <Outlet />
    </div>
  )
}

步骤二:列表页 src/routes/posts/index.tsx

import { createFileRoute, Link } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/')({
  component: PostList,
})

function PostList() {
  const posts = [
    { id: '1', title: 'Headless 哲学' },
    { id: '2', title: 'Query 缓存' },
  ]
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          {/* params 同样类型安全:缺字段或多字段都会编译报错 */}
          <Link to="/posts/$postId" params={{ postId: post.id }}>
            {post.title}
          </Link>
        </li>
      ))}
    </ul>
  )
}

步骤三:详情页 src/routes/posts/$postId.tsx

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  component: PostDetail,
})

function PostDetail() {
  // Route.useParams() 返回精确的 { postId: string },自动收窄
  const { postId } = Route.useParams()
  return <h1>文章 #{postId}</h1>
}

步骤四:组装入口 src/main.tsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { createRouter, RouterProvider } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({ routeTree })

// 把路由类型注册到全局,Link/useParams 等才可获得完整类型提示
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>,
)

关键点解析:

  • routeTree.gen.ts 由插件在 dev/build 时重新生成,新增路由文件后保存即可
  • createFileRoute('/posts/$postId') 的路径字符串由插件校验,与文件位置不匹配会报错
  • <Link> 的 to、params、search 全部受类型约束——这是 Router 与其他路由器的核心差异

Outlet 与嵌套布局

<Outlet /> 是子路由的渲染出口。父路由组件负责”公共外壳”,子路由负责”内容区域”:

__root.tsx          → 导航栏 + <Outlet />
posts/index.tsx     → 列表内容
posts/$postId.tsx   → 详情内容

带目录的布局(如 posts/_layout.tsx)可以让 /posts/* 下的所有页面共享一个不占路径的包装层,详见 Router 进阶要点。

最佳实践

路由参数属于某个路由契约。详情页读取该路由的类型化参数,导航时传入 params,能让拼错名称或漏参数更早暴露;参数在 URL 中出现仍不代表已通过业务验证。例如 id 有正确的字符串类型,也可能对应不存在的记录。

文件路由项目修改源路由,不手改生成树。用一次“列表 → 详情 → 返回 → 直接刷新详情”的实验检查地址、选中导航和返回状态是否一致,不能只验证点击后出现了页面。


练习与实践

练习一:三页骨架

  • [ ] 实现 首页 / 关于 / 文章详情 三个路由,导航栏含当前页高亮
  • [ ] 故意把 params 写错字段名,观察 TypeScript 报错信息

练习二:参数路由

  • [ ] 给详情页加一个”上一篇/下一篇”按钮,用 Link + 递增的 postId 跳转
  • [ ] 打开 routeTree.gen.ts,找到自己路由对应的节点与类型

相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇