Skip to content
导航与链接

导航与链接

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 函数

beforeLoadloader 中使用 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。