关于 vue-router
面向 Vue 应用开发者的 TanStack Router 技能指南,介绍 Vue 绑定、基于文件的路由、类型安全导航与组合式 API,并说明其默认在客户端运行加载器。
主要功能
- 01基于文件的路由配置
- 02类型安全的链接与导航
- 03Vue 路由组合式 API
- 04路由数据与搜索参数
- 05代码拆分与错误处理
适用场景
- 01在 Vue 应用中配置 TanStack Router
- 02使用类型化参数构建路由
- 03阻止含未保存更改时的导航
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tanstack/ router --skill vue-router安装后在 Claude Code 中输入/vue-router确认已加载。
详细介绍
Vue Router(@tanstack/vue-router)
此技能基于 router-core。请先阅读 @tanstack/router-core#router-core,了解基础概念。
此技能介绍 TanStack Router 的 Vue 专属绑定、组件、组合式函数和设置方法。
CRITICAL:TanStack Router 的类型完全由推断得出。切勿进行类型转换,也不要为推断值添加类型注解。
CRITICAL:TanStack Router 以客户端为先。Loader 默认在客户端运行,而非服务器端。
CRITICAL:大多数组合式函数会返回
Ref<T>——在脚本中通过.value访问,在模板中则会自动解包。这是它与 React 版本最主要的区别。
CRITICAL:不要将
@tanstack/vue-router与vue-router(Vue 官方路由器)混淆。它们是完全不同的库,API 也各不相同。
完整设置:使用 Vite 进行基于文件的路由
1. 安装依赖
npm install @tanstack/vue-router
npm install -D @tanstack/router-plugin @vitejs/plugin-vue-jsx
2. 配置 Vite 插件
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
// MUST come before vue()
tanstackRouter({
target: 'vue',
autoCodeSplitting: true,
}),
vue(),
vueJsx(), // Required for JSX/TSX route files
],
})
3. 创建根路由
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/vue-router'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<nav>
<Link to="/" activeProps={{ class: 'font-bold' }}>
Home
</Link>
<Link to="/about" activeProps={{ class: 'font-bold' }}>
About
</Link>
</nav>
<hr />
<Outlet />
</>
)
}
4. 创建路由文件
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/vue-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome Home</h1>
}
5. 创建 Router 实例并注册类型
// src/main.tsx
import { createApp } from 'vue'
import { RouterProvider, createRouter } from '@tanstack/vue-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
declare module '@tanstack/vue-router' {
interface Register {
router: typeof router
}
}
const app = createApp(RouterProvider, { router })
app.mount('#root')
组合式函数参考
所有组合式函数均从 @tanstack/vue-router 导入。大多数会返回 Ref<T>——在脚本中通过 .value 访问,或在模板中自动解包。
useRouter() — 返回 TRouter(不是 Ref)
import { useRouter } from '@tanstack/vue-router'
const router = useRouter()
router.invalidate()
useRouterState() — 返回 Ref<T>
订阅路由器状态变化。它会暴露完整状态,因此会带来性能开销。需要获取匹配项或位置信息时,优先使用 useMatches 和 useLocation。
import { useRouterState } from '@tanstack/vue-router'
const isLoading = useRouterState({ select: (s) => s.isLoading })
// Access: isLoading.value
useNavigate() — 返回一个函数(不是 Ref)
import { useNavigate } from '@tanstack/vue-router'
const navigate = useNavigate()
async function handleSubmit() {
await saveData()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
useSearch({ from }) — 返回 Ref<T>
import { useSearch } from '@tanstack/vue-router'
const search = useSearch({ from: '/products' })
// Access: search.value.page
useParams({ from }) — 返回 Ref<T>
import { useParams } from '@tanstack/vue-router'
const params = useParams({ from: '/posts/$postId' })
// Access: params.value.postId
useLoaderData({ from }) — 返回 Ref<T>
import { useLoaderData } from '@tanstack/vue-router'
const data = useLoaderData({ from: '/posts/$postId' })
// Access: data.value.post.content
useMatch({ from }) — 返回 Ref<T>
import { useMatch } from '@tanstack/vue-router'
const match = useMatch({ from: '/posts/$postId' })
// Access: match.value.loaderData.post.title
其他组合式函数
useMatches()—Ref<Array<Match>>,所有活跃的路由匹配项useRouteContext({ from })—Ref<T>,来自beforeLoad的上下文useBlocker({ shouldBlockFn })— 有未保存的更改时阻止导航useCanGoBack()—Ref<boolean>useLocation()—Ref<ParsedLocation>useLoaderDeps({ from })—Ref<T>,Loader 依赖值useLinkProps()— 返回LinkHTMLAttributesuseMatchRoute()— 返回一个函数;调用该函数会返回Ref<false | Params>
组件参考
RouterProvider
import { RouterProvider } from '@tanstack/vue-router'
// In createApp or template
<RouterProvider :router="router" />
Link
类型安全的导航链接,带有用于显示激活状态的作用域插槽:
<Link to="/posts/$postId" :params="{ postId: '42' }">
View Post
</Link>
<Link to="/about">
<template #default="{ isActive }">
<span :class="{ active: isActive }">About</span>
</template>
</Link>
Outlet
渲染匹配到的子路由组件。
Navigate
声明式重定向(在 onMounted 中触发导航)。
Await
用于延迟数据的异步设置组件——与 Vue 的 <Suspense> 搭配使用。
CatchBoundary
使用 Vue 的 onErrorCaptured 实现错误边界。
Html 和 Body
Vue 专属的 SSR 外壳组件:
function RootComponent() {
return (
<Html>
<head>
<HeadContent />
</head>
<Body>
<Outlet />
<Scripts />
</Body>
</Html>
)
}
ClientOnly
仅在 onMounted 后(即水合完成后)渲染子组件:
<ClientOnly fallback={<div>Loading...</div>}>
<BrowserOnlyWidget />
</ClientOnly>
Vue 专属模式
使用 createLink 创建自定义链接组件
import { createLink } from '@tanstack/vue-router'
import { defineComponent, h } from 'vue'
const StyledLinkComponent = defineComponent({
setup(props, { slots, attrs }) {
return () => h('a', { ...attrs, class: 'styled-link' }, slots.default?.())
},
})
const StyledLink = createLink(StyledLinkComponent)
渲染函数(h())
@tanstack/vue-router 中的所有组件内部都使用 h() 渲染函数。路由组件既可以使用 SFC 模板,也可以使用渲染函数:
MyRoute.component.vue 中的 SFC 模板(用户代码中最常用):
<template>
<div>{{ data.title }}</div>
</template>
<script setup>
import { useLoaderData } from '@tanstack/vue-router'
const data = useLoaderData({ from: '/posts/$postId' })
</script>
使用 Router 上下文进行身份验证
import { createRootRouteWithContext } from '@tanstack/vue-router'
const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
component: RootComponent,
})
const router = createRouter({
routeTree,
context: { auth: authState },
})
// In a route — access via beforeLoad
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
}
用于代码拆分的 Vue 文件约定
使用 autoCodeSplitting 时,Vue 路由可以选择采用拆分文件约定。这并非必需——单文件 .tsx 路由也可以正常工作。拆分文件适合将路由配置与组件分开:
myRoute.ts— 路由配置(搜索参数、loader、beforeLoad)myRoute.component.vue— 路由组件(延迟加载)myRoute.errorComponent.vue— 错误组件(延迟加载)myRoute.notFoundComponent.vue— 未找到页面的组件(延迟加载)myRoute.lazy.ts— 延迟加载的路由选项
常见错误
1. 严重:忘记在 script 块中使用 .value
组合式函数会返回 Ref<T>——在 <script> 中通过 .value 访问。在模板中会自动解包。
// WRONG — accessing Ref without .value in script
const params = useParams({ from: '/posts/$postId' })
console.log(params.postId) // undefined!
// CORRECT — use .value
const params = useParams({ from: '/posts/$postId' })
console.log(params.value.postId)
2. 高:与 vue-router(官方版)混淆
@tanstack/vue-router 不是 vue-router。不要使用 vue-router 中的 <router-view>、<router-link>、useRoute()、useRouter()。
// WRONG — official vue-router imports
import { useRoute, useRouter } from 'vue-router'
// CORRECT — TanStack Vue Router imports
import { useMatch, useRouter } from '@tanstack/vue-router'
3. 高:在 beforeLoad 或 loader 中使用 Vue 钩子
beforeLoad 和 loader 不是组件的 setup 函数,而是普通的异步函数。不能在其中使用 Vue 组合式函数。请改用路由器上下文传递状态。
4. 中:插件目标设置错误
必须在路由器插件配置中设置 target: 'vue'。默认值为 'react'。
交叉引用
@tanstack/router-core#router-core— 所有针对特定领域模式的子技能(搜索参数、数据加载、导航、身份验证、SSR 等)