首页/新闻资讯/正文详情

Vue Router 从入门到进阶:路由模式、守卫与权限控制的完整实战指南

发布时间:2026/9/24 23:14:31 来源:云帆数科 栏目:资讯中心
Vue Router 从入门到进阶:路由模式、守卫与权限控制的完整实战指南
做前端的人大概都绕不开 Vue Router。只要是 Vue 单页应用想要有多个页面、想要让用户在不同视图之间跳转、想要在刷新之后还能停留在当前页面而不是回到首页基本都要靠 Vue Router 来解决。它是 Vue.js 官方推出的路由解决方案最初叫 vue-router后来 Vue 3 时代变成了 Vue Router 4.x如今几乎是所有 Vue 项目的事实标准。这篇内容我打算把 Vue Router 从基础到进阶的关键环节都梳理一遍包括路由模式的选择、动态路由和路由传参、嵌套路由、路由守卫、权限控制以及它和 Pinia、ESLint Prettier、Vitest 这些常用工具的配合方式。也会把我在实际项目里踩过的坑、排查过的诡异问题比如 Vue.js DevTools 插件突然打不开这类情况一并整理出来。如果你正在用 Vue 3 做项目或者打算从 Vue 2 升级到 Vue 3这篇内容应该能帮你省下不少时间。1. 先搞懂路由在 Vue 项目里到底解决什么问题1.1 单页应用为什么需要路由早期的网站是典型的多页应用用户访问/about时浏览器向服务器请求 about 页面服务器返回一整份 HTML用户访问/contact时再请求另一份 HTML。这种模式的缺点是每次跳转都要重新加载整个页面用户体验割裂首屏渲染也快不起来。Vue 这类现代前端框架带来了单页应用SPA即 Single Page Application的开发模式整个应用只有一份 HTML页面的切换不通过浏览器刷新而是在 JS 运行时动态替换视图内容。这个模式下问题就来了——没有路由的话用户点击关于我们按钮页面上该显示什么内容用户刷新浏览器后浏览器会向服务器请求当前 URL如果服务器只认识根路径其他路径一律 404那就尴尬了。Vue Router 就是用来解决这个问题的。它负责做两件事第一监听浏览器 URL 的变化第二根据 URL 找到对应的组件并渲染到页面上。URL 在这里变成了一种状态路由则是这个状态与视图之间的映射表。1.2 两种核心模式hash 模式和 history 模式Vue Router 支持两种运行模式hash 模式和 HTML5 History 模式。我说的运行模式本质上就是路由如何与浏览器 URL 建立映射的不同实现策略。hash 模式是 Vue Router 3.x 的默认模式URL 形如https://example.com/#/about路由信息放在#号后面。#号后面的内容变化不会被浏览器当作新的请求发送到服务器因此不需要服务器做任何额外配置部署到任何静态文件服务器上都能跑。缺点是 URL 带个#不够美观而且#号后面的部分也不会被搜索引擎正常索引。history 模式是 Vue Router 4.x 的推荐模式URL 形如https://example.com/about看起来和传统多页应用一模一样。它利用 HTML5 History API 中的pushState和replaceState方法修改浏览器地址栏而不触发页面刷新。优点是 URL 简洁美观缺点是刷新页面时服务器必须把所有前端路由对应的路径都重写到index.html否则一刷新就 404。这是很多初学者上线后踩的第一个大坑。我把两种模式的核心差异整理成了一张表对比项hash 模式history 模式URL 形式带#号不带#号更美观是否需要服务器配置不需要静态服务器即可需要将所有路径重写到 index.html对 SEO 友好度较差较好但仍需预渲染或 SSR 配合兼容性极好需要支持 History API 的浏览器使用场景快速原型、无服务器配置权限的项目正式上线、关注 URL 美观的项目我在实际开发中只要项目不是特殊的临时场景基本都用 history 模式然后在部署阶段配置好服务器的 rewrites 规则。这个组合的体验是最好的。2. 路由配置里的核心操作从基础映射到动态路由2.1 基础路由表的定义方式Vue Router 4.x 的路由配置核心是一个createRouter调用里面传入routes数组。每一个路由记录route record至少包含path和component两个字段path是 URL 路径component是对应要渲染的组件。import { createRouter, createWebHistory } from vue-router import Home from /views/Home.vue import About from /views/About.vue const routes [ { path: /, name: home, component: Home }, { path: /about, name: about, component: About } ] const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) export default router这里有几个容易忽略的细节。name字段不是必填的但强烈建议填上。命名路由的好处是在编程式导航router.push时不需要写死路径直接通过name跳转后期如果调整了path只要name不变所有跳转逻辑都不用改。另外一个细节是createWebHistory里传入的import.meta.env.BASE_URL这是 Vite 里配置的 base 路径如果你把应用部署在服务器的子目录下这里要同步调整不然子目录下刷新页面时路由路径会错乱。2.2 动态路径参数解决同一类页面但不同 ID的问题真实项目里我们经常需要这样的页面每个用户有一个个人主页URL 为/user/123、/user/456页面结构完全相同只是展示的数据不同。如果为每个用户单独写一条路由记录既不现实也不合理。Vue Router 用动态路径参数解决这个问题在路径中用冒号声明参数占位符const routes [ { path: /user/:id, name: user-detail, component: UserDetail } ]这里的:id就是动态参数/user/123和/user/456都会匹配到这条路由记录。在组件内部通过route.params.id就能拿到当前 URL 中的 ID 值script setup import { useRoute } from vue-router const route useRoute() console.log(route.params.id) /script这里有一个比较隐蔽的坑在 Vue 3 中如果从/user/123直接导航到/user/456也就是只是参数变了但是匹配到的同一个组件实例组件的setup函数不会重新执行因为 Vue 的组件复用机制会直接复用之前的组件实例。这时需要监听route.params的变化来决定是否要重新拉数据。常用的写法是用watchimport { watch } from vue import { useRoute } from vue-router const route useRoute() watch(() route.params.id, (newId, oldId) { // 发现参数变化重新请求数据 fetchUserInfo(newId) })很多新手在这个问题上绕了很久明明 URL 变了页面数据却不刷新。原因就在这个组件复用的机制上理解了这一点后面排查问题会快很多。2.3 动态路由和动态添加路由权限控制的基础Vue Router 4.x 还有一个非常实用的能力动态添加路由。应用在运行过程中可以通过router.addRoute方法往路由表里添加新的路由记录这个能力是后台管理系统做权限控制的基石。常见的做法是用户登录后根据用户的角色或权限列表动态生成路由表并添加到路由实例中。默认项目里只注册公开路由login、register、404 等登录成功后再把用户有权访问的页面路由动态加上去。// 权限控制模块 import router from /router const menuRoutes [ { path: /admin, name: admin, component: () import(/views/admin/AdminDashboard.vue) }, { path: /admin/users, name: admin-users, component: () import(/views/admin/UserManage.vue) } ] // 登录成功后根据自己的业务逻辑把当前用户有权限访问的动态路由添加到路由实例 menuRoutes.forEach(route { router.addRoute(route) })动态路由有一个很容易踩的坑动态添加的路由如果没有显式地在页面中通过router-link或router.push跳转而是直接刷新浏览器动态添加的路由记录会丢失因为整个前端应用重新加载了路由表回到了最初的状态。所以刷新后必须重新拉取权限列表并重新 addRoute。正规一点的方案是把用户路由表或权限标识持久化到 localStorage 或 Pinia 中应用启动时先读取权限数据、再动态注册路由。这个过程要设计好时序不然会出现刷新后白屏或者 404 的问题。2.4 路由传参方式query、params 和 propsVue Router 支持三种主要的传参方式很多新手对它们的适用场景不清楚。query 方式URL 形式为/search?keywordvuepage2参数暴露在 URL 的查询字符串中。使用router.push({ path: /search, query: { keyword: vue, page: 2 } })跳转。查询参数的一大特点是不参与路由匹配不管 query 怎么变匹配的路由记录不变。适合搜索条件、分页参数、排序方式这类非必须但有参考价值的信息。params 方式URL 形式为/user/123。使用router.push({ name: user-detail, params: { id: 123 } })。注意这里必须先通过name指定路由不能只用pathparams。Vue Router 官方文档明确说params只有和name配合时才会生效。这个限制很多初学者踩过router.push({ path: /user/123, params: { id: 123 } })是不行的或者更准确地说如果用了 path 方式还带着 params参数会被静默丢掉。我自己开发时只对动态路径参数才用params配合name其他情况一律pathquery这样最不容易出问题。props 方式这是最推荐的解耦思路。路由可以给组件传递 props让组件不再直接依赖route对象这样组件既可以用在路由页面里也可以独立使用在普通组件中提升复用性和可测试性。const routes [ { path: /user/:id, name: user-detail, component: UserDetail, props: true } ]设置了props: true后/user/123匹配到 UserDetail 组件时组件的 props 中会直接拿到idscript setup const props defineProps({ id: { type: String, required: true } }) // 直接使用 props.id不需要 useRoute() /script这种方式对单元测试特别友好。测试组件时直接传入 props不用再去模拟整个 router 实例。3. 嵌套路由和路由守卫管理复杂页面的两把钥匙3.1 嵌套路由页面里套页面的正确打开方式大型后台管理系统经常有这种布局侧边栏和顶栏是固定的内容区根据路由切换。这就是典型的嵌套路由场景。Vue Router 通过子路由配置实现const routes [ { path: /dashboard, component: () import(/layouts/MainLayout.vue), children: [ { path: , name: dashboard-home, component: () import(/views/dashboard/Home.vue) }, { path: orders, name: dashboard-orders, component: () import(/views/dashboard/Orders.vue) }, { path: settings, name: dashboard-settings, component: () import(/views/dashboard/Settings.vue) } ] } ]子路由定义的关键在于嵌套路由的 path 不用写父级路径的前缀。children中配的是orders最终 URL 是/dashboard/ordersVue Router 会自动拼接。还有一个小技巧children中有一条记录 path 为空字符串那么访问/dashboard时就会渲染到对应的子组件上。这是实现父级路由地址打开默认子页面的标准做法非常常用。嵌套层级深了之后要注意的地方是每一层的 router-view 都要正确地放在对应层级的布局组件里。父级路由的组件模板中要有一个router-view /占位符子路由的组件才会渲染在父级组件内部。template div classmain-layout aside classsidebar!-- 侧边栏 --/aside header classheader!-- 顶栏 --/header main classcontent router-view / /main /div /template3.2 全局前置守卫登录态校验和页面访问控制路由守卫是 Vue Router 中拦截跳转流程的钩子函数。我最常使用的是全局前置守卫router.beforeEach它会在每次路由跳转前被调用是校验登录状态的理想位置。router.beforeEach((to, from, next) { const isLoggedIn localStorage.getItem(token) // 白名单不需要登录就能访问的路径 const publicPages [/login] if (!publicPages.includes(to.path) !isLoggedIn) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })这个例子是很多管理后台的基础逻辑没有 token 去访问受保护页面就跳转到登录页并且把原来想访问的路径通过redirect参数带过去。登录成功后可以读route.query.redirect来跳回用户原本想去的页面这个体验比登录后总是回首页好得多。Vue Router 4.x 的守卫函数仍然保留了三参数的next风格但实际上如果只做放行或重定向两件事可以直接省略next参数改成router.beforeEach((to) { const isLoggedIn localStorage.getItem(token) const publicPages [/login] if (!publicPages.includes(to.path) !isLoggedIn) { return { path: /login, query: { redirect: to.fullPath } } } // 没有返回值就表示放行 })这种写法更简洁也避免了调用了 next 但后续还有逻辑执行这种容易导致死循环的隐患。3.3 动态路由加守卫完整权限控制链路把动态路由和路由守卫串起来就是一个比较完整的权限控制方案。我建议这样设计流程应用初始化时注册公开路由比如/login、/register、/404。全局前置守卫里判断如果用户未登录且访问的是受保护页面跳转登录页带上 redirect 参数。用户已登录但发现路由表里尚未加载动态路由可以用一个标志位判断比如 Pinia 里的routesLoaded是否为 true就向后端请求当前用户的权限菜单根据返回的菜单数据调用addRoute批量注册。动态路由注册完成后主动调用一次next({ ...to, replace: true })重新进入当前路由让新注册的路由规则生效然后再放行。后续的每次跳转因为routesLoaded已经是 true直接放行。这个流程里有一个关键点为什么动态添加路由后还要next({ ...to, replace: true })因为当前的导航在进入守卫时Vue Router 已经基于老的、不包含动态路由的路由表完成了匹配。如果你不重新触发一次导航即使 addRoute 成功了当前这次导航也不会命中新路由结果就是页面 404 或者空白。重新导航一次实际上是让路由匹配过程基于新的路由表再来一遍这样用户当前访问的地址才能正常映射到对应的动态组件。3.4 其他常用守卫组件内守卫和全局后置钩子除全局前置守卫外还有全局解析守卫router.beforeResolve、全局后置钩子router.afterEach以及组件内的onBeforeRouteUpdate和onBeforeRouteLeave。全局后置钩子不参与导航控制但很适合做一些埋点统计和页面标题更新router.afterEach((to) { document.title to.meta.title ? ${to.meta.title} - 管理系统 : 默认标题 })这个to.meta.title在路由配置里定义const routes [ { path: /dashboard, name: dashboard-home, component: () import(/views/dashboard/Home.vue), meta: { title: 工作台 } } ]组件内守卫中onBeforeRouteLeave我用的比较多的是处理表单未保存离开确认。用户在一个编辑页面上修改了内容没保存就想跳走这时弹一个确认框防止误操作丢数据。这个在 Vue 3 的组合式 API 里可以这样写import { onBeforeRouteLeave } from vue-router const isDirty ref(false) onBeforeRouteLeave(() { if (isDirty.value) { const shouldLeave window.confirm(当前页面有未保存的修改确定要离开吗) if (!shouldLeave) { return false // 返回 false 取消导航 } } })4. 和 Vue Router 配套的工具链DevTools、Pinia、ESLint Prettier、Vitest4.1 Vue.js DevTools 插件打不开的问题排查我在项目里经常遇到一个现象Vue.js devtools (v5)插件 Chrome 浏览器上点开面板空白或者根本没有被激活。最开始我还以为是插件坏了换了插件版本也没用。排查一圈之后发现大多数时候问题出在以下几个方面开发服务器端口没匹配上。DevTools 插件默认在本地开发时只对 HTTP 协议生效而且很多浏览器插件对localhost的处理比较敏感。如果你用127.0.0.1访问插件可能激活但用localhost访问时又正常反过来也可能。这个没有统一的规律我建议开发时统一访问地址不要今天localhost明天127.0.0.1换来换去。Vue Router 版本或 Vue 版本不匹配。Vue.js DevTools v5 主要面向 Vue 3如果你的项目本身是 Vue 2应该装对应 Vue 2 的 DevTools 版本。老版本插件在 Vue 3 项目里会表现为面板打不开或者只能看到选择 Vue 版本文档之类的页面。我曾经在一个项目里同时装了新旧两个 DevTools 扩展结果两个都没法正常工作禁用掉旧版本之后立刻就好了。应用开启了生产模式。DevTools 对生产模式下的 Vue 实例支持有限。如果你在代理服务器或者测试环境里直接跑的是npm run build之后的静态文件Vue 实例跑在生产模式下DevTools 经常识别不到。这种时候想看调试面板建议直接本地起开发服务器。最后还有一类稍微隐蔽的情况项目里如果自己写了自定义的 devtools 初始化逻辑或者在某些严格 CSP 环境下插件的注入脚本会被拦截。我之前在做一个内部管理系统时登录前有一段 CSP 策略限制了script-src导致 DevTools 某些内部状态检测失败。后来把调试环境下 CSP 关掉插件就正常了。4.2 Pinia 和 Vue Router 配合的几个关键点Pinia 是 Vue 3 生态下的状态管理库常和 Vue Router 一起用。两者的分工是Vue Router 管理路由状态Pinia 管理业务状态。但实际开发中它们必然会产生交集最常见的场景就是前面提到的路由加载状态、用户权限数据放在 Pinia 里。有几个配合使用的注意点第一Pinia 的 store 不能在路由配置文件里直接 import 使用。原因是路由配置文件router/index.js往往在应用实例创建之前就被加载了而 Pinia 实例还没有 mount 到应用上。如果在模块顶层就直接useUserStore()会报 getActivePinia was called with no active Pinia 的错误。解决办法是在守卫函数内部再调用useUserStore()因为守卫是运行时才执行的届时 Pinia 已经初始化完成。// router/index.js import { useUserStore } from /stores/user router.beforeEach((to) { // 在守卫内部再调用 useStore不要写在模块顶层 const userStore useUserStore() if (to.meta.requiresAuth !userStore.isLoggedIn) { return { path: /login } } })第二通过路由参数驱动的加载状态适合放组件内不建议全局 store 化。比如route.params.id决定的详情页数据放组件内部用ref管理就够了。全局 store 适合放跨路由共享的信息比如登录用户信息、权限列表、全局配置项。如果每进入一个详情页都把数据塞进 store不仅内存占用不必要地增长还会引入数据过期、多路由交叉污染的一系列问题。4.3 ESLint Prettier 对路由代码的规范作用很多团队在搭建 Vue 项目时会用 ESLint Prettier 做代码规范检查。路由模块的代码看似简单但写出规范性的问题很常见。比如有人会在routes数组里混合使用() import()和直接引入组件虽然 Vue Router 两者都支持但为了首屏加载性能应该统一使用懒加载的形式。ESLint 规则里可以配置强制约束。// 建议写法统一用动态 import 懒加载路由组件 const routes [ { path: /about, name: about, component: () import(/views/About.vue) } ] // 不推荐全部静态引入打包时首屏 bundle 会很大路由懒加载失去意义 // import About from /views/About.vue // const routes [ // { path: /about, name: about, component: About } // ]Prettier 则主要负责代码格式统一单引号还是双引号、行尾是否有分号、tab 宽度是多少等等。如果你们团队喜欢 Prettier 自动格式化建议在提交前加一个 lint-staged 钩子保证提交到仓库的路由配置代码格式统一diff 也干净很多。对于涉及meta字段比较多的路由表我个人的建议是给路径、名称、组件懒加载、meta 里的权限标识都加上清晰的注释这样后续维护的人不用逐行推断。有时候路由表很长一屏看不完加注释的成本很低收益却很高。4.4 Vitest 单元测试路由相关逻辑怎么测Vitest 是 Vite 生态的单元测试框架很多 Vue 3 项目用它替代 Jest。路由相关的逻辑测试有一个天然的难点Vue Router 依赖浏览器原生 History API但 Vitest 默认跑在 Node 环境中。所以测试路由相关逻辑时通常要用createMemoryHistory模式。import { createRouter, createMemoryHistory } from vue-router import { describe, it, expect } from vitest function setupRouter() { const router createRouter({ history: createMemoryHistory(), routes: [ { path: /, component: { template: divHome/div } }, { path: /user/:id, component: { template: divUser Detail/div } } ] }) return router } describe(router test, () { it(should show user detail page, async () { const router setupRouter() router.push(/user/123) await router.isReady() expect(router.currentRoute.value.params.id).toBe(123) }) })createMemoryHistory是专门为测试环境设计的它不依赖真实浏览器 URL把路由历史放在内存里。配合router.isReady()等待路由初始化完成可以稳定地断言当前路由状态。如果是测试组件中使用了useRoute()、useRouter()的部分可以借助 Vue Test Utils 的挂载方式把真实 router 实例传入组件import { mount } from vue/test-utils import UserDetail from /views/UserDetail.vue it(renders user id, async () { const router setupRouter() router.push(/user/42) await router.isReady() const wrapper mount(UserDetail, { global: { plugins: [router] } }) expect(wrapper.text()).toContain(42) })这里要注意挂载时机。如果组件在setup里读取route.params.id挂载前必须先完成路由跳转和isReady否则params可能为空。5. 实战中绕不开的几个典型坑5.1 路由懒加载导致的白屏和加载失败使用() import()做路由懒加载之后网上业务中可能会出现白屏一瞬的现象这是因为路由组件是异步加载的网络慢的时候跳转后需要等组件 JS chunk 下载完再渲染。Vue Router 并没有默认提供加载中的过渡动画所以会表现为跳过去之后页面空白几百毫秒。解决思路可以是配合路由守卫做一个全局loading状态或者更简单地在根组件里放一个遮罩层监听路由变化来显示和隐藏。我个人的做法是在导航过程中设置一个页面 loading保证用户有感知import { ref } from vue import { useRouter } from vue-router const router useRouter() const pageLoading ref(false) router.beforeEach(() { pageLoading.value true }) router.afterEach(() { pageLoading.value false })另外还有一个很隐蔽的坑懒加载的路由组件如果分包命名冲突会导致两个不同的页面引用到同一个 chunk 文件页面内容互相串。在 Vite 里可以通过动态 import 的/* webpackChunkName: xxx */注释或者 Vite 的 rollup output 配置来显式指定 chunk 名称从根本上避免冲突。我见过一个项目里两个路由都叫index结果打包出来后两个页面加载的 JS 文件混在一起排查了整整一个下午。5.2 404 页面要怎么配才合理Vue Router 官方推荐的 404 通配路由写法是const routes [ // ...其他路由 { path: /:pathMatch(.*)*, name: not-found, component: () import(/views/NotFound.vue) } ]这里的/:pathMatch(.*)*准确说是 Vue Router 4 里替代 Vue Router 3 中*通配符的写法。如果不写最后那个*而是只写/:pathMatch(.*)路径参数里只能拿到一段路径有些场景下无法捕获完整的原始 URL。加上*之后可以拿到含斜杠的完整路径方便在 404 页面里展示具体是哪个地址访问失败了。需要特别注意的是404 通配路由应该放在路由表的最后面。如果放在前面它会吞掉所有无法匹配的 URL导致后续的路由规则全部失效。这个顺序问题我见过太多次了几乎每个团队都会有人踩一次。5.3 路由跳完但滚动条位置没有重置SPA 的另一个体验问题是从很长的列表页跳到详情页再返回列表页时浏览器滚动条可能停留在之前的滚动位置也可能跳到顶部行为不一致。Vue Router 4 的scrollBehavior方法提供了统一控制的入口const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes, scrollBehavior(to, from, savedPosition) { // 浏览器前进后退时恢复原滚动位置 if (savedPosition) { return savedPosition } // 其他情况下滚动到顶部 return { top: 0 } } })savedPosition是浏览器在前进后退时自动保存的滚动位置。如果用户是通过前进后退按钮进入当前页面返回savedPosition可以让体验接近原生浏览器如果是普通的跳转返回{ top: 0 }让页面回到顶部避免用户以为页面卡住了。5.4 浏览器前进后退时页面状态丢失的问题这里有一个更深的体验问题后台管理系统中用户在列表页筛选了条件、翻了页、点进详情再返回列表页时筛选条件和页码全部复位了。Vue Router 本身不管这个状态需要我们自己处理。两个常见思路思路一是把筛选条件和页码放在 URL 的 query 参数里。这样浏览器前进后退时query 参数会自然跟随 URL 变化配合监听route.query重新拉取数据就能实现状态还原。这个方案最简洁推荐优先考虑。思路二是用一个全局 store 或 KeepAlive 机制缓存页面状态。用 Pinia 保存列表查询条件返回页面时优先从 store 里恢复。这个方案在数据量比较大、筛选条件很多的场景下更合适但需要处理缓存失效和清理复杂度更高。我个人在大部分项目里都用 query 方案只要筛选条件不是特别复杂把 JSON 序列化到 query 里也能接受。只有当筛选条件非常多、URL 会变得很长很难看时才会考虑强化 store 方案。6. 常用配置和工具速查6.1 Vue Router 4 常用 API 一览API用途示例createRouter创建路由实例const router createRouter({ history, routes })createWebHistory使用 history 模式createWebHistory(/base/)createWebHashHistory使用 hash 模式createWebHashHistory()createMemoryHistory使用内存模式测试环境常用createMemoryHistory()router.push编程式导航router.push({ name: about })router.replace替换当前路由不产生历史记录router.replace(/login)router.addRoute动态添加路由router.addRoute({ path: /x, component })router.beforeEach全局前置守卫router.beforeEach((to) {})useRoute获取当前路由响应式对象const route useRoute()useRouter获取路由实例const router useRouter()onBeforeRouteUpdate组件内路由更新守卫onBeforeRouteUpdate((to) {})scrollBehavior控制路由切换后的滚动行为scrollBehavior(to, from, savedPosition) {}6.2 路由配置文件的高效结构实践下来我推荐一种比较清晰的路由目录组织方式src/ ├── router/ │ ├── index.js # createRouter 实例 │ ├── routes.js # 基础路由定义 │ ├── dynamic.js # 动态添加路由的辅助函数 │ └── guards.js # 各种路由守卫逻辑 └── views/ ├── login/ ├── dashboard/ └── error/这种拆分的好处是路由配置、守卫逻辑、动态路由加载逻辑各自独立在项目规模扩大后不会变成一个大杂烩文件。守卫逻辑里面很容易越写越乱独立出来之后每次要加权限校验或埋点改动范围都很清晰。6.3 从 Vue Router 3 升级到 4 需要注意的破坏性变化如果你维护的是 Vue 2 项目考虑升级为 Vue 3 Vue Router 4需要关注这几个破坏性变更new Router()变成了createRouter()new Router的方式已废弃。mode: history变成了history: createWebHistory()。base配置项被合并到createWebHistory(base)的入参中。*通配符路由写法改成了/:pathMatch(.*)*。移除了router.match和router.getMatchedComponents等部分 API。移除了router-link上基于事件修饰符的部分写法。移除了router-view的transition和keep-alive直接绑定的旧方式建议通过插槽的方式显式组合。升级过程中最容易被卡住的就是通配符路由的写法变化和createWebHistory的 base 配置。建议升级前把项目中所有关于路由的语法先扫描一遍整理成对照表逐一修改。7. 最后的经验总结做前端这些年来我每次搭建新项目时都会按固定的套路把路由模块设计好先确定用 history 模式还是 hash 模式再设计好路由表的层级和命名规范接着把全局守卫和权限控制搭起来最后把 404 页、滚动条恢复这些体验细节补上。路由看起来只是配置文件的几个字段但它直接关系到一个项目的骨架是否清晰、后续扩展是否顺畅。有一点是我一直强调的路由设计要在项目早期就花时间想清楚不要在页面开发得差不多了才开始补路由结构。之前接手过一个项目路由全部写在了一个文件里没有任何懒加载权限校验逻辑分散在十几个页面的created钩子里页面上线后首屏加载慢得离谱改起来也费劲。后来花了整整两个迭代把这些重构到统一的守卫和动态路由体系里性能才恢复正常。如果你现在正好在规划新项目或者最近被路由相关的 bug 折磨希望这篇内容能帮你少走一些弯路。Vue Router 本身并不复杂真正复杂的往往是它和业务逻辑、构建配置、浏览器行为之间的各种边界情况。把这些边界情况处理好你的项目基础就算打牢了。

相关推荐

基于Flask与uni-app的班级事务管理系统开发实战
基于Flask与uni-app的班级事务管理系统开发实战

说实话,班级里的那点事,看起来小,真正做起来能烦死人。班长要发通知、统计活动报名、安排值日、记录考勤,学习委员要收作业、登记成绩,文体委员要组织活动,所有信息都挤在微信群聊天记录里,翻起… · 2026/9/24 23:14:31

电动汽车充电负荷计算怎么做?蒙特卡洛模拟与MATLAB建模实战
电动汽车充电负荷计算怎么做?蒙特卡洛模拟与MATLAB建模实战

1. 为什么电动汽车充电负荷计算非要“随机”着算:蒙特卡洛的核心思路先聊一个实际感受。做电动汽车充电负荷研究,最怕的就是把问题想简单了。很多人一开始的思路是:设定一个固定的充电起始时间、固定的充电功率、固定的电池容量,然… · 2026/9/24 23:14:31

Vue Router核心解析:从路由模式到动态路由与守卫实战
Vue Router核心解析:从路由模式到动态路由与守卫实战

直接开始,Vue Router,咱们不整虚的。这几个字在Vue生态里什么分量,做前端的都懂。一个项目但凡有点规模,单页应用(SPA)的路由管理基本就是它说了算。今天这篇不聊官方文档的复读机内容,单纯把我… · 2026/9/24 23:14:31

【C++11】C++11新型语法的引入
【C++11】C++11新型语法的引入

目录 一,统一的列表初始化 1-1,初始化列表 1-2,initializer_list初始化容器 二,类型声明 2-1,auto用法的修改 2-2,decltype关键字 三,STL容器的变化 四,右值引用和移动语义 … · 2026/9/25 2:14:35

Hypothesis 递归数据生成全解:用 st.recursive 打造树、JSON 与任意嵌套结构
Hypothesis 递归数据生成全解:用 st.recursive 打造树、JSON 与任意嵌套结构

测试开发工具 【免费下载链接】hypothesis The property-based testing library for Python 项目地址: https://gitcode.com/gh_mirrors/hy/hypothesis 点击查看 免费下载 本文聚焦 Hypothesis 属性测试库中最具威力的策略之一 —— st.recursive。当你需要生成树形… · 2026/9/25 2:14:35

使用 AWS SDK for C++ 操作 Amazon SNS:从 Hello World 到发布/订阅的完整代码示例实战指南
使用 AWS SDK for C++ 操作 Amazon SNS:从 Hello World 到发布/订阅的完整代码示例实战指南

示例工程教程后端 【免费下载链接】aws-doc-sdk-examples Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below. 项目地… · 2026/9/25 2:14:35

OpenPencil 开源设计编辑器全景解读:.fig 兼容、AI 原生与完全可编程的 Figma 替代方案
OpenPencil 开源设计编辑器全景解读:.fig 兼容、AI 原生与完全可编程的 Figma 替代方案

前端桌面应用AI 应用MCP 服务 【免费下载链接】open-pencil AI-native design editor. Open-source Figma alternative. 项目地址: https://gitcode.com/gh_mirrors/op/open-pencil 点击查看 免费下载 OpenPencil 是一个 AI 原生的开源设计编辑器,定位为… · 2026/9/25 2:14:29

cuDF pylibcudf 列工厂(column_factories)API 完全指南:从空列创建到底层实现
cuDF pylibcudf 列工厂(column_factories)API 完全指南:从空列创建到底层实现

数据分析数据工程机器学习 【免费下载链接】cudf cuDF - GPU DataFrame Library 项目地址: https://gitcode.com/gh_mirrors/cu/cudf 点击查看 免费下载 导读 本文围绕 pylibcudf 的 pylibcudf.column_factories 模块展开,系统讲解如何以零数据、仅凭… · 2026/9/25 2:14:29

BentoML Flax 模型接入指南:save_model、load_model 与 get 的完整用法与底层实现解析
BentoML Flax 模型接入指南:save_model、load_model 与 get 的完整用法与底层实现解析

模型推理服务人工智能后端大模型MLOpsLLMOps 【免费下载链接】BentoML The easiest way to serve AI apps and models - Build Model Inference APIs, Job queues, LLM apps, Multi-model pipelines, and more! 项目地址: https://gitcode.com/gh_mirrors/be/BentoM… · 2026/9/25 2:14:29

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码