资讯动态

Vue Router学习笔记:从路由配置到守卫避坑的完整实践

发布时间:2026/10/24 5:54:22 来源:尧图企业网站定制
学Vue到第六天这是个特别微妙的节点。前五天我还在跟模板语法、计算属性、组件通信较劲真要把一个多页面应用跑起来的时候才发现Vue项目如果没有路由就像一栋只有房间没有走廊的楼——每个组件都做得再精致也没法让用户在页面之间自由走动。所以从第六天开始我正式把Vue Router拉进了学习清单这一整理就从早上折腾到晚上。这篇内容不是官方文档的翻译也不打算讲太多空泛的理论。我把自己第六天从零安装Vue Router、配置路由、传参、嵌套、守卫到逐个排查报错的过程完整复盘下来。如果你Vue基础语法已经过关、正在纠结“多个页面怎么跳转”或者已经装了路由但总是遇到“跳了地址栏却不变”“刷新就404”这类怪问题这篇应该能帮你少走不少弯路。1. 为什么第六天该碰路由了1.1 组件切换不等于页面跳转前五天里我经常用v-if或v-show去控制页面显示。思路很简单一个变量等于1显示首页等于2显示列表页等于3显示详情页。这种写法应付两三个tab问题不大但想真正做一个应用的时候问题就会集中爆发地址栏永远是同一个URL刷一下页面就回到最初状态浏览器后退按钮点了完全没反应别人想直接打开你正在看的那个页面更是无从下手。你可能会说多包几个v-if不就行了实际做过就知道当页面层级超过两层状态变量会爆炸式增长而且代码里全是一坨一坨的显示逻辑后期维护基本靠猜。更关键的是这种切换本质上只是“某个区域的内容替换”浏览器根本不知道你换了页面所以地址、历史记录、刷新状态这些全都跟不上。这也是路由存在的根本原因。Vue Router要做的事说穿了就三件让地址栏跟着页面内容改变让浏览器前进后退能配合内容切换让刷新之后还能停留在当前页面。它不是锦上添花的功能而是单页应用能不能真正往“应用”方向走的骨架。1.2 路由解决了什么为了把路由的价值讲清楚我对比了一下“用v-if硬切”和“用路由切页”的体验差异这样理解起来更直观能力v-if/v-show切换Vue Router切换地址栏变化不变永远是根路径随路由变化能直观看到当前位置刷新恢复直接回到初始状态刷新后停留在当前路由对应的页面浏览器前进/后退不响应按钮变灰正常记录历史能来回跳链接分享所有人打开都是同一个页面可分享具体页面带参数也能复原代码结构页面内容堆在一个组件里每个页面独立职责清晰权限控制基本靠手动判断有路由守卫统一拦截看完这张表你大概能理解为什么第六天必须碰路由再往后学组件通信、状态管理、接口请求都需要建立在“项目有多个页面”的基础上。没有路由后面那些内容都会变得很奇怪。1.3 这天之前你需要掌握什么在正式开始之前稍微确认一下自己的基础。第六天学路由最好已经知道Vue的单文件组件怎么写props和emit大概怎么通信ref/reactive这类响应式API在组件里怎么用。不用精通能用就行。如果这些还没完全吃透也不影响先跑通路由等遇到组件内部逻辑卡住时再回头补也不迟。另外后面所有示例默认你现在用的是Vue 3 Vite的工程路由插件版本对应Vue Router 4。如果你还在Vue 2 Vue Router 3思路类似但API差别比较大最好先切到Vue 3再继续。2. 路由的两种工作模式hash还是history2.1 hash模式地址栏里那个#号刚开始学路由你可能会注意到地址栏里会出现一个#比如http://localhost:5173/#/home。这就是hash模式。hash模式的原理其实不复杂URL中#后面的部分叫hash只改变hash值不会触发浏览器向后端发起请求但会触发hashchange事件同时浏览器会把这个变化记入历史记录。Vue Router在hash模式下就是监听hashchange事件再按照hash对应的路径去匹配组件渲染。这种模式最大的优势是部署省心。因为#后面的内容不会发到服务端所以随便找个静态文件服务器托管都能跑甚至双击本地html文件在大多数场景下也能正常跑起来。我当时用hash模式的时候真的是零配置完全不用管服务端要做什么。缺点也很明显URL里带着#不好看而且不够“正经”。有的场景下比如做分享、做埋点统计还要额外处理#带来的参数解析多少有点别扭。2.2 history模式更像真实的多页面写完hash模式后我又切到了history模式。这种模式下URL看起来就是干净的那种http://localhost:5173/home和传统多页面应用的地址几乎没区别。它背后的机制是基于History API核心是pushState和replaceState这两个方法。它们可以在不刷新页面的情况下修改浏览器地址栏并生成历史记录。配合popstate事件就能做到“地址变了但页面没闪一下”。听起来是比hash模式优雅但代价是它需要服务端配合。因为地址栏变成一个真正的URL之后浏览器刷新时会把请求发到服务端。如果服务端没有把/home这样的路径全部重定向到index.html那刷新就直接404。开发环境之所以没暴露这个问题是因为Vite的dev server自带了对history模式的支持会自动把所有路径兜回index.html但上线部署后如果忽略这步立刻翻车。2.3 我当时怎么选的我第六天的选择很简单直接用history模式。原因有两个。第一我练这个项目就是为了体验真实上线的感觉不想等部署时再换到时还要检查一堆路由跳转有没有问题。第二开发阶段Vite已经帮我处理了刷新兜底日常敲代码不会受任何影响等部署时我再在服务端加一个try_files配置就能解决。当然如果你只是为了快速做一个小demo完全不想操服务端的心hash模式也不丢人。在练习阶段两种模式切换的成本很低代码改动不过几行的事重点是理解它们各自在什么场景下会踩坑。3. 从零配置一个可用路由核心三步3.1 安装依赖并创建Router实例先把插件装上我用的是Vue Router 4npm install vue-router4然后单独建一个路由配置文件我习惯放在src/router/index.js里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(), routes }) export default router这里有几个点需要解释。createRouter是Vue Router 4创建实例的方法routes数组就是路由表里面每个对象都表示“某个路径对应哪个组件”。createWebHistory()表示使用history模式如果你在上一步决定用hash模式把它换成createWebHashHistory()就行。我在学习时把组件直接静态引入是因为这样能直观看到路由表结构。实际项目里更常见的写法是用动态导入做路由懒加载比如component: () import(../views/About.vue)。好处是首屏不会一次加载所有页面的代码访问到对应页面时才去请求该页面的JS文件。第六天打基础阶段可以先了解但别觉得“动态导入是高手才写的”其实就是一个函数箭头包一层很好理解。3.2 注册全局Router并放置页面出口路由实例创建了还得让整个Vue应用知道它的存在。在main.js里做两步import { createApp } from vue import App from ./App.vue import router from ./router const app createApp(App) app.use(router) app.mount(#app)app.use(router)这一步是关键它会注册路由相关的全局组件和生命周期逻辑。如果不做这一步后面在组件里写router-link或useRoute都会直接报错。然后在App.vue里放一个router-view组件。这个组件就是路由的“出口”路由匹配到哪个页面组件它就会渲染哪个。再放一个或多个router-link作为导航入口template div nav router-link to/首页/router-link router-link to/about关于/router-link /nav router-view / /div /templaterouter-link最终会被渲染成一个a标签但它和普通a标签有区别普通a标签点击后浏览器会重新加载整个页面而router-link默认会拦截点击用路由内部的逻辑切换内容避免整页刷新。这个细节很值得体会它决定了单页应用的“快”是快在哪里的。3.3 两个容易写错的小细节第一次配路由时我犯了不少糊涂有两个细节值得单独提醒第一routes里的path不要带文件后缀也不要在末尾加.html。/about就写/about不要写/about.html或者/about/如果是根路径/除外。原因很简单路由是在管理应用的页面状态不是真的指向某个物理文件。第二redirect写绝对路径别省略前面的斜杠。比如{ path: /a, redirect: /b }如果写成redirect: b浏览器解析时会按相对路径处理结果就不是你想去的那个地址了。这种错误很好排查点击跳转后看地址栏对比一下目标地址是http://localhost:5173/b还是http://localhost:5173/current/b一眼就能看出问题。4. 路由传参四种姿势怎么选4.1 query传参适合搜索条件和分享场景先写第一种方式query传参。它把参数拼在路径后面和传统URL的?参数一样router.push({ path: /detail, query: { id: 10086 } })跳转之后地址栏会变成/detail?id10086。在目标组件里通过useRoute拿参数import { useRoute } from vue-router const route useRoute() console.log(route.query.id)query传参最大的好处是参数完全暴露在URL里刷新不丢而且分享给别人时对方打开链接就能恢复页面状态。所以搜索页、筛选条件、分页页码这类场景用query很合适。它也有局限如果放太复杂的对象URL会变得巨长而且难以阅读。遇到这种情况更建议只传ID或关键标识拿到标识后自己再查一遍完整数据。4.2 params传参与命名路由语义化更强第二种方式会让参数直接出现在路径里。比如详情页往往希望地址是/detail/10086而不是/detail?id10086。这时要先把路由表里的path配置成带占位符的形式{ path: /detail/:id, name: detail, component: Detail }跳转时有两条路一条是直接用path拼接字符串router.push(/detail/10086)另一条是用命名路由把参数放在params里router.push({ name: detail, params: { id: 10086 } })这里有一个新手必踩的坑很多教程会告诉你params传参但如果你在跳转时用的是path而不是nameparams会被直接忽略。也就是说下面这种写法传不过去// 这样不行 router.push({ path: /detail, params: { id: 10086 } })因为Vue Router在解析时path的优先级很高一旦提供了pathparams就被扔掉了。想用params就老老实实用name或者直接自己拼字符串。这个坑我踩了之后才知道官方文档也写得很清楚只是平时跳来跳去容易忽略。4.3 用props解耦让组件更干净无论用query还是params组件里拿参数都得靠useRoute()这意味着组件和路由对象耦合在一起。如果有一天想把这个详情组件用在非路由的场景还得再传一遍props非常别扭。更好的做法是开启路由的props选项{ path: /detail/:id, name: detail, component: Detail, props: true }开启后路由会自动把params里的字段作为props传给组件。组件里直接声明props即可defineProps([id])这样一来组件内部不再依赖useRoute也方便单元测试和维护。对于params里那些值props: true会作用在动态路由段的参数上。如果你还希望query参数也能映射为props就需要把props写成函数形式手动处理一下{ path: /detail, component: Detail, props: (route) ({ id: route.query.id }) }两种写法我都试过日常开发中能开props: true就尽量开组件会清爽很多。4.4 参数变化时组件不会重新创建这是我在做详情页时遇到的一个隐藏问题从/detail/1跳到/detail/2如果这两个路径都用同一个Detail组件Vue会复用这个组件实例不会重新走一遍挂载流程。也就是说onMounted里的初始化逻辑不会再次执行。这意味着在详情页里光靠生命周期函数去拉数据是不行的。解决方案一般是在组件里监听路由参数watch( () route.params.id, (newId) { // 拿到新id重新请求详情 }, { immediate: true } )加上immediate: true之后组件第一次进入时就会执行一次后面参数变化也会触发逻辑就能统一起来。如果对这个了解不深非常容易写出“第一次跳转正常、第二次跳转不刷新”的bug。5. 嵌套路由与路由守卫实际项目躲不开的功能5.1 嵌套路由页面里还有页面第六天学到后面我开始做一个带多个子页面的布局。比如用户中心上面一个统一的侧边栏左侧是导航右侧是不同子页面的内容。这种场景很适合嵌套路由也就是路由里套路由。配置方式是在父路由下面加一个children数组{ path: /user, component: UserLayout, children: [ { path: , component: UserProfile }, { path: posts, component: UserPosts }, { path: settings, component: UserSettings } ] }注意两点children里的path不要以/开头否则会被当成根路径去匹配path: 表示在父路径下默认渲染的内容访问/user时就会显示UserProfile。父组件UserLayout里需要再放一个router-view这样子路由匹配到的组件才能在指定位置渲染。我在第一次写的时候总忘了在父组件里加这个出口结果子页面永远是空白排查了好一会儿才发现是视图出口没放。嵌套的层级理论上不限只要每一层都有对应的router-view承接就行。实际项目中我目前最多用到三层层级太深会让路由表变得复杂维护成本明显上升。5.2 beforeEach全局守卫登录拦截的常规做法路由守卫是我觉得Vue Router最值得写一下的部分。原因很简单它把一个“页面能不能进”的问题从组件内部逻辑中彻底抽离出来统一在路由层处理。最常用的是全局前置守卫beforeEach。它会在每次路由跳转前执行可以用来做登录拦截。比如进入任何页面之前检查localStorage里有没有token没有就去登录页router.beforeEach((to, from) { const token localStorage.getItem(token) if (!token to.path ! /login) { return /login } return true })这里的返回值很灵活返回false表示取消本次跳转返回一个路径表示重定向到那个路径不写或者返回true表示放行。上面示例里如果用户没登录且要去的不是登录页就直接被扔到登录页。这里有个我一开始没注意的陷阱如果你在守卫里重定向到了/login而/login本身也被同一个守卫拦住那就可能死循环。所以我在判断条件里加了to.path ! /login确保登录页永远放行。除了beforeEach还有afterEach它在跳转完成后执行适合做页面标题更新、页面访问统计这类“事后”任务。当然守卫不止这两种但第六天能先把全局守卫吃透就很够用了。5.3 meta路由元信息给页面加备注路由配置里的每一项还可以挂一个meta字段用来存一些和页面有关的信息。最常见的就是页面标题{ path: /about, name: about, component: About, meta: { title: 关于我们 } }然后在全局afterEach里统一设置document.titlerouter.afterEach((to) { document.title to.meta.title ? ${to.meta.title} - Vue Day6 : Vue Day6 })另外meta还可以用来标记某个页面是否需要登录权限、菜单里要不要高亮、统计分类是什么等等。这样在守卫里可以组合判断比如router.beforeEach((to) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { return /login } })把权限需求和路由表写在一起比在组件里到处写判断干净得多。6. 第六天实测几个常见路由报错与排查思路6.1 “No match found”与404页面配置路由配置好了点击跳转时控制台可能报No match found for location with path /xxx。这个报错的意思很简单当前路径在你的路由表里匹配不到任何记录。我当时排查的逻辑是几步走先看地址栏里的路径到底是多少再翻路由表里有没有对应的path如果路由表里有动态路由段/detail/:id还要确认实际访问的路径有没有正常填上参数。有时候是因为嵌套路由的children里path写多了/导致父路径没匹配上。这种问题不难定位因为报错信息里会把当前路径原样打出来。更好的做法是给路由表加一个兜底配置让所有没匹配上的路径都指向一个404页面{ path: /:pathMatch(.*)*, name: not-found, component: NotFound }注意Vue Router 4里通配符的写法和Vue Router 3不一样不再是*而是/:pathMatch(.*)*。如果按旧写法配置可能在升级项目时会踩到坑。把这个404路由放在最后表示前面所有路由都匹配不上才会走到这里。6.2 页面路径变了但内容没变还有一个我折腾很久的现象点击router-link后地址栏的URL确实变了但页面内容纹丝不动。排这个问题的思路一般是先确认router-view有没有放对位置。如果在根组件里没有router-view或者在某个子组件里又写了一个遮盖层把内容挡住了就会出现“地址变了但什么都看不到”的情况。还有一个容易忽略的点是如果router-view前面有v-if控制显示条件条件不满足时路由匹配正常但视图就出不来。更隐蔽的情况是路由表里写了两个相同path的配置前面的组件不生效后面的又没被匹配到结果路由解析时按顺序匹配到了空组件。总之遇到“地址对了但内容没来”先把路由表和router-view存在性这两件事查一遍能解决八成问题。6.3 刷新后404问题的根源在服务端这个是history模式下最经典的坑。开发环境几乎不会出现因为dev server已经帮你把不存在路径的请求都兜回index.html了。但一旦部署到生产环境服务端处理不当时你在某个子页面一刷新浏览器就会向服务器发一个真实请求服务器找不到这个路径对应的文件于是返回404。解决思路也很明确让服务端把前端子路径的请求统一重新定向到index.html由前端路由自己决定渲染谁。以常见的nginx为例需要在站点配置里加上location / { try_files $uri $uri/ /index.html; }意思是优先尝试访问真实文件或目录都找不到时就回退到index.html。这样刷新后浏览器加载的是index.htmlVue Router会解析当前地址重新渲染对应页面。如果你用的是hash模式就不存在这个问题因为#后面的内容不会发到服务端。从这一点也能理解为什么很多人图省事会选hash模式。但我的网站想保持干净美观的URL所以还是选了history模式上线前给服务端补好配置就行。这项配置不要等到最后一刻才想起部署前就应该写在部署文档里。第六天过完路由在我心里的分量从“一个会跳转的组件占位符”变成了“整个应用状态的骨架”。我后来写页面时会习惯性先想这个页面属于哪个层级参数应该放URL里还是query里需不需要守卫拦截这些思考基本都是从那天的报错和试错中长出来的。最后分享一个自己的小习惯每次新建路由页面我都顺手在路由表里给它写上meta.title这样到后期做’多标签页’或‘面包屑’功能时直接有数据可用。路由这东西入门只需要一小时但要在真实项目里不踩坑每一层细节都值得多花几分钟琢磨。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑