关于 tanstack-router
用于 TanStack Router 的开发技能,适合 React 开发者构建类型安全的路由。内容涵盖基于文件的路由、搜索参数验证、loader、访问守卫和类型化导航。
主要功能
- 01类型安全的路由与导航
- 02基于文件的路由约定
- 03验证搜索参数
- 04通过 loader 加载数据
- 05使用 beforeLoad 检查访问权限
适用场景
- 01为 React 应用添加类型化路由
- 02验证查询参数
- 03在加载数据前保护路由
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill tanstack-router安装后在 Claude Code 中输入/tanstack-router确认已加载。
详细介绍
TanStack Router
TanStack Router 是一个完全类型安全的 React 路由器,它将路由、加载器、参数和搜索参数视为一等类型化数据,而不是无类型字符串。
添加新路由的工作流
- 创建路由文件 — 按照基于文件的路由约定(见下文),在
src/routes/下添加文件。 - 定义路由 — 从
createFileRoute('/path')({...})导出Route,并提供component,还可选提供loader、validateSearch、beforeLoad、errorComponent和pendingComponent。 - 验证搜索参数 — 如果路由会读取查询参数,请定义 Zod 架构并将其传入
validateSearch。 - 加载数据 — 在
loader中获取数据,而不是在组件的useEffect中获取;需要缓存时,通过queryClient.ensureQueryData集成 TanStack Query。 - 设置访问保护 — 添加
beforeLoad检查(例如身份验证);当前置条件不满足时,执行throw redirect({ to: '/login' })。 - 链接到路由 — 使用
<Link to="/path" params={...} search={...}>导航,以便编译器在每个调用位置验证参数和搜索参数。 - 重新生成路由树 — 运行或构建应用之前,确保已重新生成
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