关于 navigation
面向 React 应用的 TanStack Router 导航技能,介绍类型安全的路由实现。内容涵盖链接、编程式导航、预加载、导航拦截和滚动恢复。
主要功能
- 01类型安全的链接与路由参数
- 02链接激活状态
- 03相对导航与编程式导航
- 04路由预加载策略
- 05导航拦截与滚动恢复
适用场景
- 01构建带动态路由参数的链接
- 02表单提交后执行导航
- 03提前预加载待访问的路由
- 04离开前提示未保存的更改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tanstack/ router --skill navigation安装后在 Claude Code 中输入/navigation确认已加载。
详细介绍
导航
设置
使用 to 和 params 的基础类型安全 Link:
import { Link } from '@tanstack/react-router'
function PostLink({ postId }: { postId: string }) {
return (
<Link to="/posts/$postId" params={{ postId }}>
View Post
</Link>
)
}
核心模式
带活动状态的 Link
import { Link } from '@tanstack/react-router'
function NavLink() {
return (
<Link
to="/posts"
activeProps={{ className: 'font-bold' }}
inactiveProps={{ className: 'text-gray-500' }}
activeOptions={{ exact: true }}
>
Posts
</Link>
)
}
活动链接还会设置 data-status 属性为 "active",以便基于 CSS 设置样式。
activeOptions 用于控制匹配行为:
exact(默认为false)——为true时,仅匹配完全相同的路径(不匹配子路径)includeHash(默认为false)——在活动状态匹配中包含哈希includeSearch(默认为true)——在活动状态匹配中包含搜索参数
子元素可以通过渲染函数接收 isActive:
<Link to="/posts">
{({ isActive }) => <span className={isActive ? 'font-bold' : ''}>Posts</span>}
</Link>
使用 from 进行相对导航
如果不指定 from,导航会从根路径 / 解析。若要使用 .. 等相对路径,请提供 from:
import { createFileRoute, Link } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
})
function PostComponent() {
return (
<div>
{/* Relative to current route */}
<Link from={Route.fullPath} to="..">
Back to Posts
</Link>
{/* "." reloads the current route */}
<Link from={Route.fullPath} to=".">
Reload
</Link>
</div>
)
}
使用 useNavigate 进行编程式导航
仅在由副作用触发导航时(例如,表单提交后)使用 useNavigate。对于用户点击的内容,请优先使用 Link。
import { useNavigate } from '@tanstack/react-router'
function CreatePostForm() {
const navigate = useNavigate({ from: '/posts' })
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const response = await fetch('/api/posts', { method: 'POST', body: '...' })
const { id: postId } = await response.json()
if (response.ok) {
navigate({ to: '/posts/$postId', params: { postId } })
}
}
return <form onSubmit={handleSubmit}>{/* ... */}</form>
}
Navigate 组件会在挂载时立即执行客户端导航:
import { Navigate } from '@tanstack/react-router'
function LegacyRedirect() {
return <Navigate to="/posts/$postId" params={{ postId: 'my-first-post' }} />
}
只要能访问路由器实例,就可以在任何位置使用 router.navigate,包括 React 之外。
预加载
策略:intent(悬停/触摸开始)、viewport(交叉观察器)、render(挂载时)。
全局设置:
import { createRouter } from '@tanstack/react-router'
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultPreloadDelay: 50, // ms, default is 50
})
或针对单个链接设置:
<Link
to="/posts/$postId"
params={{ postId }}
preload="intent"
preloadDelay={100}
>
View Post
</Link>
预加载的数据默认会在 30 秒内保持新鲜(defaultPreloadStaleTime: 30_000)。在此期间不会重新获取数据。如果使用 TanStack Query 等外部缓存,请将 defaultPreloadStaleTime: 0 设为 0,让外部库控制数据新鲜度。
通过路由器实例手动预加载:
import { useRouter } from '@tanstack/react-router'
function Component() {
const router = useRouter()
useEffect(() => {
router.preloadRoute({ to: '/posts/$postId', params: { postId: '1' } })
}, [router])
return <div />
}
阻止导航
表单有未保存的更改时,使用 useBlocker 阻止导航:
import { useBlocker } from '@tanstack/react-router'
import { useState } from 'react'
function EditForm() {
const [formIsDirty, setFormIsDirty] = useState(false)
useBlocker({
shouldBlockFn: () => {
if (!formIsDirty) return false
const shouldLeave = confirm('Are you sure you want to leave?')
return !shouldLeave
},
})
return <form>{/* ... */}</form>
}
使用 withResolver 自定义 UI:
import { useBlocker } from '@tanstack/react-router'
import { useState } from 'react'
function EditForm() {
const [formIsDirty, setFormIsDirty] = useState(false)
const { proceed, reset, status } = useBlocker({
shouldBlockFn: () => formIsDirty,
withResolver: true,
})
return (
<>
<form>{/* ... */}</form>
{status === 'blocked' && (
<div>
<p>Are you sure you want to leave?</p>
<button onClick={proceed}>Yes</button>
<button onClick={reset}>No</button>
</div>
)}
</>
)
}
单独控制 beforeunload:
useBlocker({
shouldBlockFn: () => formIsDirty,
enableBeforeUnload: formIsDirty,
})
使用 linkOptions 复用导航选项
linkOptions 可对导航选项对象进行即时类型检查,因此错误会在定义时而非展开时显现:
import {
linkOptions,
Link,
useNavigate,
redirect,
} from '@tanstack/react-router'
const dashboardLinkOptions = linkOptions({
to: '/dashboard',
search: { search: '' },
})
// Use anywhere: Link, navigate, redirect
function Nav() {
const navigate = useNavigate()
return (
<div>
<Link {...dashboardLinkOptions}>Dashboard</Link>
<button onClick={() => navigate(dashboardLinkOptions)}>Go</button>
</div>
)
}
// Also works in an array for navigation bars
const navOptions = linkOptions([
{ to: '/dashboard', label: 'Summary', activeOptions: { exact: true } },
{ to: '/dashboard/invoices', label: 'Invoices' },
{ to: '/dashboard/users', label: 'Users' },
])
function NavBar() {
return (
<nav>
{navOptions.map((option) => (
<Link
{...option}
key={option.to}
activeProps={{ className: 'font-bold' }}
>
{option.label}
</Link>
))}
</nav>
)
}
使用 createLink 创建自定义组件
将任意组件包装为具有 TanStack Router 类型安全导航功能的组件:
import * as React from 'react'
import { createLink, LinkComponent } from '@tanstack/react-router'
interface BasicLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {}
const BasicLinkComponent = React.forwardRef<HTMLAnchorElement, BasicLinkProps>(
(props, ref) => {
return <a ref={ref} {...props} className="block px-3 py-2 text-blue-700" />
},
)
const CreatedLinkComponent = createLink(BasicLinkComponent)
export const CustomLink: LinkComponent<typeof BasicLinkComponent> = (props) => {
return <CreatedLinkComponent preload="intent" {...props} />
}
使用时仍保留完整的类型安全:
<CustomLink to="/dashboard/invoices/$invoiceId" params={{ invoiceId: 0 }} />
滚动恢复
在路由器上全局启用:
const router = createRouter({
routeTree,
scrollRestoration: true,
})
针对嵌套的可滚动区域:
const router = createRouter({
routeTree,
scrollRestoration: true,
scrollToTopSelectors: ['#main-scrollable-area'],
})
自定义缓存键:
const router = createRouter({
routeTree,
scrollRestoration: true,
getScrollRestorationKey: (location) => location.pathname,
})
针对特定导航阻止滚动重置:
<Link to="/posts" resetScroll={false}>
Posts
</Link>
使用 MatchRoute 显示待处理 UI
import { Link, MatchRoute } from '@tanstack/react-router'
function Nav() {
return (
<Link to="/users">
Users
<MatchRoute to="/users" pending>
<Spinner />
</MatchRoute>
</Link>
)
}
常见错误
严重:将 params 插入 to 字符串
// WRONG — breaks type safety and param encoding
<Link to={`/posts/${postId}`}>Post</Link>
// CORRECT — use the params option
<Link to="/posts/$postId" params={{ postId }}>Post</Link>
动态片段使用 $ 在路由路径中声明。始终通过 params 传递它们。此规则适用于 Link、useNavigate、Navigate 和 router.navigate。
中等:对可点击元素使用 useNavigate
// WRONG — no href, no cmd+click, no preloading, no accessibility
function BadNav() {
const navigate = useNavigate()
return <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
}
// CORRECT — real <a> tag with href, accessible, preloadable
function GoodNav() {
return <Link to="/posts">Posts</Link>
}
仅在以编程方式通过副作用触发导航时(表单提交后、异步操作后等)使用 useNavigate。
高:相对导航未提供 from
// WRONG — without from, ".." resolves from root
<Link to="..">Back</Link>
// CORRECT — provide from for relative resolution
<Link from={Route.fullPath} to="..">Back</Link>
如果不提供 from,只有绝对路径会获得自动补全和类型安全。.. 等相对路径会从根路径而非当前路由解析。
高:将 search 用作对象会丢失已有参数
// WRONG — replaces ALL search params with just { page: 2 }
<Link to="." search={{ page: 2 }}>Page 2</Link>
// CORRECT — preserves existing search params, updates page
<Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>
将 search 作为普通对象传递时,会替换所有搜索参数。请使用函数形式展开之前的参数,并有选择地更新。
交叉引用
- 另请参阅:router-core/search-params/SKILL.md — Link 的
search属性会与搜索参数验证交互 - 另请参阅:router-core/type-safety/SKILL.md —
from收窄可改善 Link 的类型推断