导航与链接
TanStack Router 提供声明式 <Link> 组件和命令式 useNavigate Hook,两者都享有完整的类型安全——路径、参数和搜索参数都由 TypeScript 校验。
Link 组件
基础用法
import { Link } from '@tanstack/react-router'
// 简单路径
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
// 带路径参数
<Link to="/posts/$postId" params={{ postId: '123' }}>
文章详情
</Link>
// 带搜索参数
<Link to="/posts" search={{ page: 1, sort: 'newest' }}>
帖子列表
</Link>使用 from 属性(推荐)
from 告诉路由器当前路由的位置,从而获得更精确的类型提示:
// src/routes/posts/index.tsx
function PostsPage() {
return (
// from 让 Link 知道继承当前路由的所有搜索参数
<Link
from="/posts/"
search={(prev) => ({ ...prev, page: prev.page + 1 })}
>
下一页
</Link>
)
}💡 最佳实践:始终提供
from(当前路由路径)或to。两者都提供时类型最准确。只给to也能工作,但类型推断可能不够精确。
搜索参数的函数式更新
当需要保留现有搜索参数时,使用函数式更新器:
// ✅ 正确:函数式更新——保留所有现有参数,只更新 page
<Link
from="/posts/"
search={(prev) => ({ ...prev, page: prev.page + 1 })}
>
下一页
</Link>
// ❌ 错误:对象替换——会丢失 sort、filter 等其他参数
<Link to="/posts" search={{ page: 2 }}>
下一页
</Link>Link 的 active / inactive 状态
<Link
to="/about"
activeProps={{ className: 'font-bold text-blue-600' }}
inactiveProps={{ className: 'text-gray-600' }}
>
关于
</Link>
// 或者使用 render prop 模式
<Link to="/about">
{({ isActive }) => (
<span className={isActive ? 'font-bold' : ''}>关于</span>
)}
</Link>useNavigate Hook
用于编程式导航(事件处理、异步操作后跳转等):
import { useNavigate } from '@tanstack/react-router'
function MyComponent() {
const navigate = useNavigate({ from: '/current-route' })
const handleClick = () => {
// 简单路径跳转
navigate({ to: '/about' })
// 带参数
navigate({ to: '/posts/$postId', params: { postId: '123' } })
// 函数式搜索参数更新
navigate({
to: '/posts',
search: (prev) => ({ ...prev, page: 2 }),
})
// 替换当前历史记录(不回退到此页)
navigate({ to: '/login', replace: true })
}
}redirect 函数
在 beforeLoad、loader 中使用 redirect() 进行服务端风格的重定向:
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
// 功能类似服务端的 redirect——不是组件,而是抛出特殊对象
throw redirect({
to: '/login',
search: {
redirect: location.href, // 登录后返回原页面
},
})
}
},
})🔬 深入原理:
redirect()实际上抛出一个Redirect对象(类似 React Router 的Response抛出)。TanStack Router 在导航流程中捕获它并执行客户端重定向。这也是为什么它必须在beforeLoad/loader中使用throw关键字——利用异常机制中断执行流。
redirect 的 options
throw redirect({
to: '/login',
search: { redirect: '/dashboard' },
// 可选配置
replace: true, // 替换历史记录
statusCode: 301, // SSR 时的 HTTP 状态码
reloadDocument: false, // 是否刷新整个页面
})预加载策略(Preloading)
TanStack Router 提供三种预加载策略,在鼠标悬停或链接可见时提前加载目标路由的数据:
| 策略 | 触发时机 | 适用场景 |
|---|---|---|
intent |
鼠标悬停 / 触摸开始 | 大多数链接(推荐) |
viewport |
链接进入视口(IntersectionObserver) | 列表中的链接 |
render |
链接渲染时立即预加载 | 高频访问的关键页面 |
全局配置
// router.tsx
const router = createRouter({
routeTree,
defaultPreload: 'intent', // 默认策略
defaultPreloadDelay: 100, // 触发前延迟(ms),防止误触
defaultPreloadStaleTime: 30_000, // 预加载缓存过期时间(ms)
})单链接配置
<Link
to="/posts/$postId"
params={{ postId: '123' }}
preload="intent" // 覆盖全局设置
preloadDelay={50} // 覆盖全局延迟
>
文章详情
</Link>
// 禁用预加载
<Link to="/heavy-page" preload={false}>
重型页面
</Link>⚡ 性能提示:
intent是平衡体验和带宽的最佳默认值。对于列表中的链接,考虑使用viewport来确保用户滚动到时数据已经就绪。
useNavigate 高级用法
带状态传递
navigate({
to: '/checkout',
state: { fromCart: true, couponCode: 'SAVE20' }, // 不在 URL 中
})🚨 陷阱:
state不会出现在 URL 中,刷新页面后会丢失。它适合传递非关键的 UI 状态。需要持久化的状态请使用搜索参数。
条件导航 Guards
const navigate = useNavigate({ from: '/editor' })
const handleClose = async () => {
if (hasUnsavedChanges) {
const confirmed = await showConfirmDialog()
if (!confirmed) return
}
navigate({ to: '/documents' })
}navigate vs Link vs redirect 选择指南
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 导航菜单/按钮 | <Link> |
声明式、可访问性好、自动预加载 |
| 表单提交后跳转 | useNavigate |
命令式、可配合异步操作 |
| 认证守卫/重定向 | redirect() |
在 loader/beforeLoad 中、可携带状态码 |
| 条件跳转 | useNavigate |
需要逻辑判断 |
| 服务端渲染 301/302 | redirect() |
支持 HTTP 状态码 |
外部导航与原生链接
// 内部链接 → 用 Link
<Link to="/about">关于</Link>
// 外部链接 / 下载 / 非路由链接 → 用原生 <a>
<a href="https://github.com" target="_blank" rel="noopener noreferrer">
GitHub
</a>🚨 陷阱:不要用
<Link>处理外部 URL(如to="https://...")。TanStack Router 会尝试将其作为内部路由匹配,可能导致 404。