
在 Vue 3 项目中使用tanstack/vue-router从零搭建文件路由到响应式组合式 API 完全指南【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router本文以 TanStack Router 官方 Skill 文档 packages/vue-router/skills/vue-router/SKILL.md 为骨架结合仓库中tanstack/vue-router包的源码实现packages/vue-router/src系统讲解如何在一个 Vue 3 项目中完成 TanStack Router 的安装配置、文件路由搭建、组合式 APIComposables与组件Components的完整使用以及 Vue 特有的响应式模式与常见误区。读完本文你将能够独立搭建一个具备完全类型安全、客户端优先、可扩展 SSR 的全栈式 Vue 路由应用。前置概念先读 router-core Skill本文只聚焦 Vue 特有的绑定层bindings。TanStack Router 的核心路由匹配、数据加载loaders、搜索参数校验、导航、鉴权与 SSR 等通用概念全部沉淀在 router-core 技能文档中建议先阅读 packages/router-core/skills/router-core/SKILL.md 建立基础认知再回到本文学习 Vue 绑定层。四个必须先记住的关键事实CRITICAL在动手之前务必把下面四条准则刻进脑子里它们是使用tanstack/vue-router的游戏规则类型完全推断FULLY INFERREDTanStack Router 的类型系统会从routeTree自动推导出to、from、params、search等全部类型。永远不要手动as强转也不要给推断出来的值写类型注解——强转会直接破坏类型安全。客户端优先CLIENT-FIRST默认情况下loader 在客户端运行而非服务端。这一点决定了你在设计数据加载时的思维模式SSR 场景需要显式配置。绝大多数组合式 API 返回RefT这是与 React 版本最大的区别。在script中必须通过.value访问在模板中会自动解包auto-unwrap。例如useSearch、useParams、useLoaderData、useMatch、useRouterState等全部返回RefT而useRouter()与useNavigate()例外见下文。不要混淆tanstack/vue-router与官方vue-router这是两个完全不同的库、完全不同的 API。不要使用router-view、router-link、useRoute()、useRouter()来自vue-router的。一、完整搭建Vite 文件路由File-Based Routing1. 安装依赖npm install tanstack/vue-router npm install -D tanstack/router-plugin vitejs/plugin-vue-jsxtanstack/vue-router提供全部组合式 API 与组件tanstack/router-plugin是 Vite 插件负责扫描src/routes目录生成routeTree.gen.ts路由树文件并支持自动代码分割auto code splittingvitejs/plugin-vue-jsx是让.tsx/.jsx路由文件能够以 JSX 语法书写 Vue 组件所必需的Vue 官方 JSX 插件。在仓库中该包的 package.json 将vue^3.3.0声明为 peer dependencynode引擎要求20.19包内部依赖tanstack/router-core、tanstack/history与tanstack/vue-store响应式 store 层并提供./ssr/server与./ssr/client两个子路径导出用于 SSR。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 ], })两个关键点tanstackRouter()必须放在vue()之前必须显式设置target: vue。因为插件默认target是react这也是文档常见错误第 4 条专门强调的。autoCodeSplitting: true开启后插件会自动为路由组件做按需加载详见后文Vue 文件约定。3. 创建根路由Root Route// 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 / / ) }createRootRoute创建整个应用的根路由Link提供类型安全的导航链接to会被类型系统校验activeProps在当前路由激活时注入额外属性这里是高亮 classOutlet /渲染匹配到的子路由组件。4. 创建路由文件// src/routes/index.tsx import { createFileRoute } from tanstack/vue-router export const Route createFileRoute(/)({ component: HomePage, }) function HomePage() { return h1Welcome Home/h1 }在文件路由模式下每个src/routes下的文件导出一个通过createFileRoute(/路径)创建的Route对象。在源码层面fileRoute.ts 中的createFileRoute接收文件路径字符串仅用于类型推导返回createRoute工厂函数其中显式设置了(route as any).isRoot false以与根路由区分。注意旧的FileRoute类写法已被标记为deprecated应使用上面的函数式写法。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)关键点routeTree由插件在构建时自动生成到./routeTree.gendeclare module tanstack/vue-router中的Register接口注册是强制性的——不写这一步Link、useNavigate、useSearch等就完全没有类型安全createApp(RouterProvider, { router })将RouterProvider作为根组件挂载并传入 router 实例。从源码看RouterProvider.tsx 内部由两个组件构成RouterProvider负责渲染Matches当前匹配路由组件RouterContextProvider通过provideRouter(router)见 routerContext.tsx本质是Vue.provide(routerContext, router)把 router 实例注入整棵组件树它还会调用router.update({ ...router.options, ...restAttrs })允许通过 attrs 覆盖 router 配置并把剩余的 attrs 合并进context。若在组件树外用injectRouter()注入 router会抛出 No TanStack Router found in component tree. Did you forget to add a RouterProvider component? 错误——这也是组合式 API 必须在RouterProvider内使用的底层原因。二、组合式 APIComposables完整参考所有组合式 API 都从tanstack/vue-router导入。除了useRouter()与useNavigate()外其余绝大多数返回RefT在script中通过.value访问模板中自动解包。useRouter()— 返回TRouter不是 Refimport { useRouter } from tanstack/vue-router const router useRouter() router.invalidate()useRouter.tsx 的实现非常简单Vue.inject(routerContext, null)取出 router 实例。在非生产环境且未在RouterProvider内调用时会输出警告 useRouter must be used inside a component!。它返回原始 router 对象而非 Ref你可以直接调用invalidate()、navigate()、buildLocation()等方法。useRouterState()— 返回RefT订阅 router 状态变化。它会暴露整个 router state因此有一定性能开销——如果只需要 matches 或 location优先用useMatches和useLocation。import { useRouterState } from tanstack/vue-router const isLoading useRouterState({ select: (s) s.isLoading }) // Access: isLoading.value从 useRouterState.tsx 的实现可以看到它的内部机制它先useRouter()取得 router再读取router.stores.__storerouter-core 暴露的响应式状态 storeSSR 环境下只渲染一次、不需要响应性当isServer为真时直接store.get()取快照并用Vue.ref()包一层返回避免在服务端订阅 store服务端 store 不提供subscribe()语义客户端则通过tanstack/vue-store的useSelector订阅并返回响应式Ref传入的select函数会按选择结果做响应式订阅未传select时返回整个 state。useNavigate()— 返回函数不是 Refimport { useNavigate } from tanstack/vue-router const navigate useNavigate() async function handleSubmit() { await saveData() navigate({ to: /posts/$postId, params: { postId: 123 } }) }useNavigate.tsx 从useRouter()中取出navigate并做了一层包装支持可选的from默认值返回一个类型安全的导航函数。上面的示例演示了跳转到带路径参数的路由to与params都会被类型系统严格校验postId拼错、漏传都会在编译期报错。useSearch({ from })— 返回RefTimport { useSearch } from tanstack/vue-router const search useSearch({ from: /products }) // Access: search.value.pageuseParams({ from })— 返回RefTimport { useParams } from tanstack/vue-router const params useParams({ from: /posts/$postId }) // Access: params.value.postIduseLoaderData({ from })— 返回RefTimport { useLoaderData } from tanstack/vue-router const data useLoaderData({ from: /posts/$postId }) // Access: data.value.post.contentuseMatch({ from })— 返回RefTimport { useMatch } from tanstack/vue-router const match useMatch({ from: /posts/$postId }) // Access: match.value.loaderData.post.title从源码看useSearch、useLoaderData、useParams、useLoaderDeps等本质上都是useMatch的封装它们把select分别作用于match.search、match.loaderData、match.params等字段上见 useSearch.tsx 与 useLoaderData.tsx返回类型是Vue.RefTuseMatch.tsx 同样基于useSelector对 match store 做细粒度订阅并支持strict默认true要求精确的from与shouldThrow默认truefrom不匹配时抛错两个选项。理解这层封装关系后你就能推断出这几个组合式 API 全部支持可选的select函数来做派生选择避免不必要的重新渲染。其他组合式 API 速查组合式 API返回类型用途useMatches()RefArrayMatch所有当前激活的路由匹配useRouteContext({ from })RefT读取beforeLoad写入的路由上下文useBlocker({ shouldBlockFn })voidwithResolver: true时返回RefBlockerResolver拦截导航如未保存更改的离开确认useCanGoBack()Refboolean判断历史栈是否可以后退useLocation()RefParsedLocation当前解析后的 locationuseLoaderDeps({ from })RefTloader 依赖项的值useLinkProps()LinkHTMLAttributes手动构造 Link 的 props供createLink使用useMatchRoute()返回函数调用后返回Reffalse \| Params判断某路由是否匹配并取回参数其中useBlocker的底层实现见 useBlocker.tsx值得一提它通过watchEffect订阅history.block({ blockerFn, enableBeforeUnload })shouldBlockFn会收到{ current, next, action }三个参数当前/目标位置的 routeId、fullPath、params、search 以及历史动作类型当withResolver: true时导航被拦截后 resolver 变为{ status: blocked, current, next, action, proceed, reset }调用proceed()放行、reset()取消它还内置了从 404 页跳转到合法路由直接放行的逻辑。文档建议使用shouldBlockFn对象语法旧式blockerFn/condition写法已标记deprecated。三、组件Components参考RouterProviderimport { RouterProvider } from tanstack/vue-router // In createApp or template RouterProvider :routerrouter /应用的顶层组件负责注入 router、渲染匹配结果内部渲染Matches。它接受完整的 router 配置作为 propscontext以外的选项并支持在RouterProvider上直接覆盖 router 选项。Link类型安全的导航链接支持作用域插槽scoped slot暴露激活状态Link to/posts/$postId :params{ postId: 42 } View Post /Link !-- Scoped slot for active state -- Link to/about template #default{ isActive } span :class{ active: isActive }About/span /template /Link从 link.tsx 源码看Link底层由useLinkProps构建其返回的LinkHTMLAttributes兼容 Vue 的AnchorHTMLAttributes、ReservedProps与data-*属性并额外支持 camelCase 的鼠标/触摸事件与disabled属性内部使用useIntersectionObserver实现预加载preload并借助router-core的getUrlScheme、isDangerousProtocol、removeTrailingSlash等工具做 URL 安全处理与路径规整。SSR 环境下它只渲染一次不建立 store 订阅与观察器。Outlet渲染匹配到的子路由组件根布局中的插槽出口。Navigate声明式重定向组件——在onMounted时触发导航见 useNavigate.tsx 中的Navigate实现它直接调用useRouter().navigate并返回null不渲染任何 DOM。Await用于延迟数据deferred data的异步 setup 组件配合 Vue 的Suspense使用。CatchBoundary基于 Vue 的onErrorCaptured实现的错误边界组件。Html与BodyVue 专用的 SSR shell 组件function RootComponent() { return ( Html head HeadContent / /head Body Outlet / Scripts / /Body /Html ) }ClientOnly仅在onMounted即水合完成后渲染子内容ClientOnly fallback{divLoading.../div} BrowserOnlyWidget / /ClientOnlyfallback指定水合前的占位内容适合包住依赖浏览器 API 的组件。仓库 src 中还提供了HeadContent、Scripts、ScrollRestoration、ScriptOnce、Asset等 SSR/文档头相关组件可配合 SSR 导出tanstack/vue-router/ssr/server、tanstack/vue-router/ssr/client使用。四、Vue 专属模式Vue-Specific Patterns1. 用createLink自定义 Link 组件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)createLink将任意 Vue 组件包装成具有完整类型安全与预加载能力的 Link。它基于useLinkProps工作因此包装后的组件继承了 TanStack Router 的to/params/search类型校验。2. 渲染函数h()tanstack/vue-router内部所有组件都用h()渲染函数实现。用户的路由组件既可以用 SFC 模板也可以用渲染函数。SFC 模板写法对用户代码最常见template div{{ data.title }}/div /template script setup import { useLoaderData } from tanstack/vue-router const data useLoaderData({ from: /posts/$postId }) /script注意模板中data会被自动解包所以直接写data.title而无需data.value.title。3. 用 Router Context 实现鉴权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 }) } }createRootRouteWithContextT()声明根路由的上下文类型createRouter时注入context实现在任意路由的beforeLoad中通过context访问。这是 TanStack Router 推荐的鉴权模式因为beforeLoad和loader是普通异步函数不能使用 Vue 组合式 API所以必须通过 router context 传递共享状态详见常见错误第 3 条。4. Vue 文件约定与代码分割Code Splitting开启autoCodeSplitting后Vue 路由可以按需使用拆分文件约定——但这不是强制的单文件的.tsx路由完全正常工作。拆分文件适合把路由配置与组件分离实现按需懒加载文件内容myRoute.ts路由配置search params、loader、beforeLoadmyRoute.component.vue路由组件懒加载myRoute.errorComponent.vue错误组件懒加载myRoute.notFoundComponent.vue未找到组件懒加载myRoute.lazy.ts懒加载的路由选项对应的源码实现是 lazyRouteComponent.tsx它用 Vue 的异步组件机制包装懒加载目标并配合CatchBoundary/ErrorComponent处理加载失败。这一约定把路由配置与视图组件彻底解耦非常适合大型应用。五、常见错误Common Mistakes排查手册1. 【CRITICAL】在script里忘记写.value组合式 API 返回RefT在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. 【HIGH】与官方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-router3. 【HIGH】在beforeLoad或loader里使用 Vue HooksbeforeLoad和loader不是组件 setup 函数而是普通异步函数无法在其中使用 Vue 组合式 APIref、computed等。应改为通过 router context 传递状态见上文Router Context 鉴权。4. 【MEDIUM】Vite 插件 target 配错必须在 router 插件中设置target: vue因为默认值是react。配错会导致生成的 routeTree 与 Vue 绑定层不匹配。六、交叉参考与进一步学习router-core/SKILL.md — 覆盖搜索参数、数据加载、导航、鉴权、SSR 等领域专属模式所有子技能本包源码packages/vue-router/src — 组合式 API 与组件的完整实现可对照useMatch、RouterProvider、link、useBlocker、fileRoute等文件深入阅读包配置与导出packages/vue-router/package.json — 查看依赖tanstack/router-core、tanstack/history、tanstack/vue-store、SSR 子路径导出与 peer dependency运行时示例仓库中 examples/vue 下的basic、basic-file-based-jsx、basic-file-based-sfc三个示例分别演示了最简配置、JSX 文件路由与 SFC 文件路由的实际用法是本文所述配置的最佳落地参照。结语tanstack/vue-router把 TanStack Router 的完全类型安全 客户端优先哲学完整地带到了 Vue 3 生态文件路由由 Vite 插件自动生成routeTree全部组合式 API 返回响应式RefT与 Vue 的响应式系统无缝融合createLink、Router Context 鉴权、按文件约定的自动代码分割则提供了从 SPA 到 SSR 的完整扩展路径。只要牢记类型全推断、客户端优先、Ref 用.value访问、别和官方 vue-router 混淆四条准则你就能在 Vue 项目中获得媲美原生框架的端到端类型安全体验。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考