尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Vue3路由基础:Vue Router 4核心链路全解析
学 Vue3 逃不开路由这一关。我在带项目的时候有个很深的感触组件的语法可以边写边查响应式和状态管理也有明确的套路但路由是整个应用的地基——地基没打好后面做动态菜单、页面权限、多级嵌套布局时每一步都在还债。这篇文章是 Vue3 研学系列的第一篇不贪多把 vue-router 4 里最核心的基础链路完整过一遍单页应用为什么需要路由、createRouter 初始化、路由表怎么写、页面怎么跳、嵌套路由怎么组织、懒加载怎么加。适合已经能写基础 Vue3 组件、想系统补路由知识的同学边读边动手一个下午能全部跑通。1. 先从信息架构理解路由单页应用到底在路由什么1.1 传统多页面与单页应用的本质区别先别急着写配置我想把路由在应用里扮演的角色讲透。很多时候新手学到路由会产生一个疑问我不用路由用v-if控制组件显示不也一样能切换页面吗这个想法在真实项目里很快会碰壁。传统多页面应用里URL 和页面文件是一一对应的。你访问/about.html服务器就把这个完整的 HTML 文档发给你浏览器整页刷新、重新执行脚本。这个模型简单直观但每次跳转都有网络开销交互体验也谈不上流畅。单页应用换了一套思路服务器只维护一个入口 HTML剩下的内容全是 JS 在浏览器里动态渲染。用户点击链接本质上只是把页面上的某一块组件替换掉浏览器不刷新响应自然更快。但新问题随之而来URL 不再指向真实存在的物理文件那我怎么告诉浏览器当前展示的是哪个页面用户把地址复制发给同事对方打开后还能看到同一个界面吗刷新、前进、后退又该怎么处理1.2 路由管什么不管什么路由在单页应用里做的事情可以概括成一句话维护 URL 和组件之间的映射表。当前地址是什么就解析出对应的路由记录把匹配的组件渲染到router-view里地址栏变化时反过来触发组件切换同时把这条记录写进浏览器历史栈。你可以把路由理解成应用的地址簿。但这个地址簿有明确的责任边界。我在代码评审里经常看到有人把数据请求、权限判断也塞进路由配置里这是对路由的过度使用了。路由只解决地址对应哪个组件、导航过程怎么走这一层问题具体页面要不要登录、加载哪个接口、缓存不缓存是由导航守卫、组件逻辑、keep-alive这些配套机制负责的。搞清楚边界后面学守卫才不会乱。2. Vue Router 4 的初始化安装、history 模式和挂载顺序2.1 版本对应关系和最小工程先确认版本。Vue3 对应的路由库必须是vue-router4Vue2 项目里那套vue-router3的写法不能直接搬过来。如果用脚手架的默认模板create-vue 的 Vue Router 选项依赖会帮你装好如果是从零手搭执行一句安装命令npm install vue-router4安装完最精简的配置是两个文件。先建一个router/index.jsimport { 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然后在入口文件里挂载import { createApp } from vue import App from ./App.vue import router from ./router createApp(App).use(router).mount(#app)app.use(router)这行做了三件事把$router、$route注入到全局注册RouterLink和RouterView两个内置组件并启动首次路由导航。所以它必须在mount之前调用如果把顺序反过来页面里会报组件找不到 router这种运行时错误。2.2 createWebHistory 和 createWebHashHistory 怎么选createRouter接收的history参数是 Vue Router 4 和 Vue Router 3 最大的差异点之一。Vue2 里写mode: historyVue3 里改成传入一个 history 实例常见的有三种模式URL 示例原理适合场景createWebHistory/user/1基于 History API 的pushState/replaceStateURL 干净无#有服务端配置能力的正式项目createWebHashHistory/#/user/1监听hashchange#后面的内容不会发到服务器纯静态托管、本地快速验证createMemoryHistory不体现内存中维护历史栈不改动地址栏SSR、测试环境我一开始为了省事在个人项目里一直用 hash 模式。后来切到 history 模式时被刷新 404坑过一次才真正理解为什么官方默认推荐createWebHistory。原因很简单hash 模式里#之后的内容浏览器不会发给服务器所以静态文件服务天然支持而 history 模式的路径是真实 URL服务器找不到/user/1这个物理文件时就会返回 404。生产环境必须配置 SPA fallback比如所有未知路径都指向index.html。这个话题我在第七节会展开。2.3 router.isReady 到底什么时候用新人在看别人工程时可能见过这种写法const app createApp(App) app.use(router) router.isReady().then(() { app.mount(#app) })这不是必须的但在一种场景下很关键路由有异步导航守卫或者页面首屏要依赖路由状态做初始化。如果直接mount应用先渲染出一版空壳isReady()之后再二次更新容易造成闪烁。我自己在带鉴权逻辑的项目里会用isReady等导航稳定再挂载这样路由守卫里拿到的数据是完整可用的。3. 路由表配置静态路径、动态参数和匹配顺序3.1 路由记录的基础字段routes数组里的每一项叫一条路由记录最常用的字段就四个path定义 URLname起个全局唯一的名字component放组件children放嵌套路由。component可以直接传组件对象也可以传一个返回组件的异步函数后者就是第六节要讲的懒加载。path必须以/开头这是 vue-router 的硬性要求。很多同学第一次写路由时写了个path: home控制台立刻报错其实就是这个原因。我的习惯是每个路由都加name取名用资源-子页面的格式比如user-detail、order-pay后面跳转时按名字走路径随便改都不慌。3.2 动态段参数怎么接后台系统的详情页几乎都长这样/user/1、/post/3数字是资源 id。路由表里用冒号声明动态段const routes [ { path: /user/:id, name: user-detail, component: UserDetail } ]访问/user/42时组件里通过route.params.id拿到字符串42。注意它是字符串哪怕 URL 里写的是数字做接口请求前最好先Number()转换一下。多个动态段同理比如/post/:pid/comment/:cid参数会分别放在route.params.pid和route.params.cid里。如果想让参数带约束可以用正则。比如只匹配数字 id{ path: /user/:id(\\d), name: user-detail, component: UserDetail }这个正则语法来自 vue-router 底层的 path-to-regexp 库\\d表示一到多位数字。写上去之后/user/abc就匹配不到这条路由了会落到 404 兜底逻辑里。3.3 通配路由和 404 兜底Vue Router 4 最让老 Vue2 开发者头疼的变化之一就是通配符*被废弃了默认会警告。现在捕获所有未匹配路径要用专门的路由记录const routes [ // ...其他路由 { path: /:pathMatch(.*)*, name: not-found, component: NotFound } ]/:pathMatch(.*)*的含义是匹配任意层级的任意字符。它通常在数组最后一条用来承接一切看不懂的地址。我在后台项目里还会写一个辅助函数把用户不小心访问的路径记录下来至少能看到是哪个链接写错了。3.4 匹配优先级静态优先于动态别再靠声明顺序调了这是很多人的知识盲区。Vue Router 3 时代路由匹配严格按声明顺序来谁在前谁先生效Vue Router 4 引入了路由评分系统每条路由会根据路径的具体程度自动计算分数分数高的优先匹配。实验结果很直观同时存在/user、/user/:id、/:page访问/user时一定命中/user而不是看这三行的书写顺序。打个比方路径匹配就像查通讯录你按联系人--公司--城市层层筛选越具体的条件越靠前命中而不是按当初录入的顺序找。所以在 Vue Router 4 里除了通配路由放最后保持可读性其余路由顺序并不影响结果。这个改进让路由表容错率提高了不少但也让一些从旧文档抄代码的人踩了莫名其妙的坑——明明把动态路由写在前面了为什么静态路由还是抢走了匹配。3.5 redirect 和 alias路由配置里的两个快捷方式redirect用于把旧地址送到新地址。比如网站改版后/list换成了/articles不用修改所有外链直接配一条{ path: /list, redirect: /articles }alias则是给同一条路由多挂几个地址。比如移动端和 PC 端共用同一个商品详情页可以这样{ path: /product/:id, name: product-detail, component: ProductDetail, alias: /item/:id }我的建议是redirect随便用alias谨慎用。别名会让一套渲染逻辑对应多个 URL后面的埋点、SEO 和权限配置都要跟着处理逻辑容易糊。4. 页面跳转与参数传递声明式和编程式导航怎么配合4.1 router-link 是带状态的链接模板里的跳转用router-link最省事。它渲染成a标签但内部会拦截默认跳转行为只做组件切换不发新请求。router-link to/about关于我们/router-link router-link :to{ name: user-detail, params: { id: 42 } }用户42/router-linkto既支持字符串也支持对象。对象写法里用name params是最稳定的组合因为path一旦写死后面的路径重构就成了牵一发动全身的麻烦。4.2 两个激活类名怎么区分router-link自带两个激活类名router-link-active是包含激活router-link-exact-active是精确激活。举个例子你当前在/user/42/profile那么指向/user的链接会带router-link-active因为/user是当前路径的前缀只有指向/user/42/profile的链接才会带router-link-exact-active。导航栏菜单一般用包含激活高亮当前一级菜单面包屑或者 Tab 标签想精确对应某个二级页面时用精确激活。如果你觉得默认类名太长可以在createRouter配置里通过linkActiveClass和linkExactActiveClass改成项目统一的前缀比如active、exact-active样式文件能省不少事。4.3 编程式导航push 和 replace 的区别组件里跳转用useRouterimport { useRouter } from vue-router const router useRouter() // 新增一条历史记录可以后退回来 router.push(/users) // 带 query 参数 router.push({ name: user-list, query: { page: 2, keyword: vue } }) // 替换当前记录后退不会回到这一页 router.replace(/login) // 前进后退 router.back() router.go(-2)需要记住的核心区别是push往历史栈塞一条新记录replace把当前记录顶掉。我常用的判断标准是问自己用户后退时该回哪。比如登录成功后用replace(/home)用户按后退键会跳过登录页而不是绕回来再填一次表单列表页进详情页用push(/detail/1)用户能顺理成章地返回到之前滚动的位置。4.4 query 和 params哪个活着更久这是最容易写出隐藏 bug 的地方。query会出现在 URL 的问号后面比如/users?page2keywordvueparams对应动态段比如/user/42里的42。有两条铁律必须记清楚。第一path和params不能混用。写router.push({ path: /user, params: { id: 42 } })时params会被直接忽略控制台还会给出警告。想靠params带参数必须用name params的写法。第二只有在路径里声明过的动态段刷新后才会留在 URL 上。临时想带一个跟路径无关的数据搜索词、来源标记老老实实用query否则刷新一次参数就没了。4.5 同一组件下参数变化组件复用引发的血案当/user/1跳到/user/2时两个地址匹配的是同一条路由记录、同一个组件。Vue 会复用组件实例不会重新走created、mounted生命周期。我第一次被这个问题坑时详情页数字变了但接口没重发查了半天才发现数据还是第一个用户的。正确做法是监听参数变化import { watch } from vue import { useRoute } from vue-router const route useRoute() watch( () route.params.id, (newId) { fetchUser(newId) }, { immediate: true } )更路由原生的姿势是用onBeforeRouteUpdate守卫。两者都能解决复用问题前者通用性更强后者只在路由切换时触发语义更清晰。我自己的习惯是轻量监听用watch需要配合取消旧请求、处理 loading 状态时用onBeforeRouteUpdate。5. 嵌套路由children 与布局组件的关系5.1 嵌套解决的到底是哪个问题后台管理系统里最典型的布局是左侧菜单 顶部栏固定不动只有中间内容区跟着路由切换。如果每个页面都写一遍侧边栏代码会膨胀到没法维护。嵌套路由的解决思路是把公共布局做成父组件在父组件里放一个router-view子路由的页面渲染到这个插槽里。父组件只负责壳子路由决定瓤。这个模式还能继续套比如用户中心下面再分个人资料、我的订单两级层级多深都行。5.2 配置结构长什么样const routes [ { path: /user, component: () import(../views/UserLayout.vue), children: [ { path: , name: user-home, component: () import(../views/UserHome.vue) }, { path: profile, name: user-profile, component: () import(../views/UserProfile.vue) }, { path: post/:postId, name: user-post, component: () import(../views/UserPost.vue) } ] } ]父组件模板里必须有对应的出口template div classuser-layout aside用户中心菜单/aside main router-view / /main /div /template子路由的path有两种写法效果截然不同不带斜杠的profile会拼在父路径后变成/user/profile这是嵌套的常规用法带斜杠的/profile则会脱离父级变成顶层路由访问路径直接是/profile父组件渲染不出来。项目里我一般规定子路由一律不带斜杠只有真正需要独立成页的场景才例外。5.3 空路径和重定向让父路径本身也能有内容上面的配置里访问/user时父路径匹配成功但children里没有path: 的话内容区是空的。这种父路径即入口的场景有两种处理方式一是加一个空路径子路由让/user直接渲染首页内容二是用重定向把/user送到具体子路由children: [ { path: home, name: user-home, component: UserHome } ] // 父路由上加 redirect redirect: /user/home两种方案我都用过。如果/user这个地址本身有合理的展示内容用空路径如果它只是一个目录性质的入口用重定向更干净。注意path: 的子路由如果声明了name会占据父路径对应的 name 注册后续要格外小心命名冲突。5.4 嵌套路由的 params 继承规则嵌套路由的动态参数会自动向下穿透父路由是/user/:userId子路由是profile那么访问/user/42/profile时子组件里route.params.userId依然能取到42。这个继承特性在做详情页的子 Tab 时非常有用不需要每一层子路由都重新声明父级参数。但要注意一个陷阱子路由访问父级参数时如果子路由内部又定义了同名参数子级会覆盖父级。命名时尽量错开比如父用userId子用postId这种命名即文档的做法能让后续维护的人少踩很多坑。6. 路由懒加载与异步组件首屏别把所有页面都扛上6.1 为什么要做路由级代码分割项目小的时候所有页面打进一个 bundle 好像没啥感觉页面一多首屏加载的 JS 可能几百 KB 甚至上兆。懒加载的本质是访问哪个页面才加载哪个页面的代码块。构建工具碰到动态import()会自动做代码分割把每个路由组件拆成独立的 chunk首屏只加载入口那一个小包。6.2 懒加载的标准写法{ path: /user/:id, name: user-detail, component: () import(../views/UserDetail.vue) }就这么一句component从普通对象变成了返回 Promise 的箭头函数。打包后你会看到dist/assets下多了一堆命名过的 js 文件而不是一个巨大的主包。打开浏览器 Network 面板切换路由时能清楚地看到对应的 chunk 是按需请求的。如果想给 chunk 起个可读的名字便于排查webpack 工程里可以用魔法注释component: () import(/* webpackChunkName: user-detail */ ../views/UserDetail.vue)Vite 工程默认按文件路径生成 chunk 名不用额外配置但要注意保证.vue文件命名能看懂别出现一排index-xxxx.js。6.3 具名导出组件的大坑多数人写的.vue文件都是默认导出懒加载写法问题不大。但如果你在组件文件里用了具名导出export const UserDetail { /* ... */ }那么直接() import(../views/UserDetail.vue)会拿不到组件运行时报Component is undefined。此时要手动取具名导出component: () import(../views/UserDetail.vue).then((m) m.UserDetail)我在接手一个历史项目时被这个坑耽误过一晚上排查到最后才发现是导出的问题。团队如果组件文件没有做统一规范建议在路由层写一个工具函数统一处理默认/具名导出能省掉一批类似的疑难杂症。6.4 异步组件的加载状态处理懒加载意味着首次进入页面会有一段网络等待。Vue3 里可以用Suspense包裹异步组件提供一个加载中的 fallback。不过路由级懒加载通常加载速度很快状态闪烁反而突兀我更推荐的做法是配合顶部的全局 loading 条而不是每个页面都套Suspense。数据请求的 loading 交给页面内部自己管理路由 chunk 的加载交给全局进度条两者各司其职体验反而更好。7. 首轮实践复盘几个真实场景的踩坑记录7.1 从 Vue Router 3 迁移到 4习惯要改三个地方网上很多教程还是 Vue2 时代的路由写法直接搬到 Vue3 项目里会连续报错。最典型的三处差异第一初始化从new VueRouter({ mode: history, routes })变成createRouter({ history: createWebHistory(), routes })第二通配符*变成/:pathMatch(.*)*第三router-link的exact、tag等属性被移除了相关能力分别由exact-active-class和router-view包裹来承担。如果你是从旧项目迁移先把这几个点改掉大部分编译报错都能消失。7.2 history 模式下刷新 404不是代码的问题这个问题几乎每个用createWebHistory的人都会遇到。本地开发时开发服务器自带 fallback一切正常一旦部署到纯静态服务或某台没配规则的服务器在/user/42刷新就直接 404。排查时要先分清是不是配置问题直接访问根路径正常深链接刷新 404就是服务端没做 SPA fallback。最常见的修复是给静态服务器加一条规则nginx 示例location / { try_files $uri $uri/ /index.html; }意思是没有匹配到真实文件时兜底返回index.html让前端路由去接管。如果你只是本地快速验证、或者托管平台不允许改服务端配置就暂时退回createWebHashHistory先保证逻辑跑通部署方案后面再补。7.3 参数变了页面没反应先怀疑组件复用详情页从/user/1跳到/user/2数据没变、页面没刷新十有八九是踩了第三节和第四节反复强调的组件复用问题。排查时可以先用onBeforeRouteUpdate打个日志看路由有没有触发如果触发了但视图没更新那问题在组件内部对参数变化没响应。解决方式就是加watch(() route.params.xxx)。这个知识点值得单独标记成团队新人手册第一条因为它出现的频率实在太高了。7.4 组件外部使用路由别在 setup 里找不到 router我们习惯在组件里用useRouter()但它必须在setup函数作用域内调用。如果在普通工具函数、store模块、或者异步回调里想跳转不要重复造轮子直接导入之前创建好的路由实例import router from /router export function redirectToLogin() { router.replace(/login) }这个写法简单可靠也是我在项目里处理 401 跳转的标配方案。记住一条原则路由实例是全局单例导出来就能用不需要每个模块都新建。7.5 路由表顺序不是万能的别用它抑制匹配最后一节再提醒一次 Vue Router 4 的匹配机制。我已经不止一次看到有人在动态路由后面补一条静态路由来抢匹配结果发现不生效——因为评分系统不看声明顺序。遇到匹配不符合预期优先检查路径是不是写错了、正则约束是不是太严而不是靠调顺序碰运气。通配路由放最后是约定俗成的可读性规范但它的兜底性质并不依赖必须在最后来实现。第一篇的内容到这里正好告一段落。上面这些配置和问题基本覆盖了平时写页面跳转时 80% 的场景。我个人的学习节奏是先搭一个带三个页面、一个嵌套布局的 demo把router-link、push、replace、参数监听全部跑一遍然后主动造几个 bug——写错 name、混用 pathparams、用老通配符一个个把报错看明白比背语法高效得多。下一篇我会接着整理路由守卫和动态路由权限那是后台系统绕不开的第二道坎也是路由从跳转工具升级成权限骨架的关键一步。
RELATED

相关推荐

Spring @Required 注解废弃解析:依赖注入校验演进与构造器注入迁移

Spring @Required 注解废弃解析:依赖注入校验演进与构造器注入迁移

1. 一个被遗忘的注解,为什么还值得聊Required这个注解,如果你是从 Spring Boot 时代入行的,大概率没见过它,甚至在 IDE 里敲出来会发现它上面压着一道醒目的删除线。它属于那种"存在于文档里、消失在代码里"的元老级 AP…

📅 2026/10/12 10:43:23
C++实战:课程安排中的约束建模与搜索优化

C++实战:课程安排中的约束建模与搜索优化

1. 从"排课"这个老问题说起:为什么值得用C认真做一遍课程安排这件事,看起来是个教务处的活儿,但真正动手写过的人都知道,它本质上是一个带约束的组合优化问题。每学期开始前,教务老师面对的是几十个班级、上…

📅 2026/10/12 10:43:23
电磁场与电磁波教案:时变场与麦克斯韦方程组的课堂推导

电磁场与电磁波教案:时变场与麦克斯韦方程组的课堂推导

简介:面向电子信息类专业《电磁场与电磁波》课程的教案文档,适合高校教师备课、课后复盘及学生自学,内容覆盖法拉第电磁感应定律、位移电流、麦克斯韦方程组及不同介质交界面边界条件等核心知识点,强调积分形式与微分形式的对应转…

📅 2026/10/12 10:43:23
MORE NEWS

更多资讯

📰

AI Agent编排层:企业级生产系统的操作系统内核

1. 项目概述:这不是又一个“AI Agent”概念秀,而是编排层正在从后台走向台前最近两周,如果你关注技术动态,大概率会注意到两个看似不相关的信号:华为在某次闭门技术分享中首次系统性拆解了其内部AI Agent平台的“调度中…

📰

Scrapy工程化实战:从爬虫脚本到生产级数据管道

1. 这不是又一个“Hello World”式爬虫教程,而是你真正能用Scrapy接住生产流量的起点Scrapy框架这个词,最近半年在某高校计算机系的实训课、某在线教育平台的Python进阶班、还有不少自学转行者的GitHub星标列表里,出现频率明显高了。但很多人…

📰

AI编排层:多Agent协同的神经中枢与工程落地指南

1. 项目概述:这不是又一个“AI Agent”概念秀,而是编排层正在从后台走向台前最近两周,如果你关注技术动态,大概率会注意到两则看似独立、实则共振的消息:一家国内头部科技企业发布了名为“星河智控”的新一代AI工作流引…

📰

Python计算器开发:从表达式解析到图形界面与打包

1. 为什么我不建议你一上来就写图形界面很多人看到"用Python写计算器"这个题目,第一反应就是打开搜索引擎,找一段Tkinter的代码复制粘贴,跑起来看到一个窗口能点按钮,就觉得任务完成了。我早期也是这么干的,…

📰

AI日报从0到1:信息筛选、内容撰写与持续运营的完整方法论

1. 一份"AI日报"到底在记录什么每天早上打开信息流,铺天盖地的AI新闻标题扑面而来,但真正值得花时间读的其实没几条。我做AI日报这个项目快两年了,从最开始随手在备忘录里记几笔,到现在形成一套固定的筛选、整理、输出流…

📰

提示注入攻击与深度防御:Python构建AI应用安全防线的完整实践

1. 当AI应用开始“中毒”:提示注入是怎样的隐形威胁 从我个人的实战体感来说,近两年接触的AI应用项目中,最容易被忽略但又最容易翻车的安全问题,不是模型本身的性能,而是 提示注入攻击(Prompt Injection&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬