Vue Router 基础使用得先搞懂哪些?
很多刚接触 Vue 框架的同学,一提到路由配置、页面跳转这些功能就犯愁,Vue Router 作为 Vue 生态里处理单页应用路由的核心工具,其实把原理和操作理清楚后没那么复杂,这篇文章用问答形式,把 Vue Router 从基础使用到进阶技巧,再到踩坑解决全拆解开,帮你实实在在掌握它~
要玩转 Vue Router,基础环节得把安装配置、页面渲染、路由模式这些核心点吃透。
安装与路由实例创建
Vue 项目里要用 Vue Router,得先装依赖(Vue2 选 vue-router@3
,Vue3 选 vue-router@4
):
npm install vue-router # 按项目版本选对应版本
装完后,在 src/router/index.js
里初始化路由:
// Vue2 示例(Vue3 写法类似,API 有差异) import Vue from 'vue' import VueRouter from 'vue-router' import HomeView from '../views/HomeView.vue' Vue.use(VueRouter) // 注册路由插件 // 定义路由规则:path 对应 URL,component 对应渲染的组件 const routes = [ { path: '/', name: 'home', component: HomeView // 直接导入组件(静态加载) }, { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') // 懒加载,后面讲 } ] // 创建路由实例,配置模式和规则 const router = new VueRouter({ mode: 'history', // 路由模式:history 或 hash(默认 hash) routes }) export default router
最后在 main.js
把路由注入 Vue 根实例:
import Vue from 'vue' import App from './App.vue' import router from './router' new Vue({ router, // 注入路由,让所有组件能通过 this.$router/$route 访问 render: h => h(App) }).$mount('#app')
页面里的路由渲染与跳转
路由配置好后,页面得用两个核心标签:
-
<router-view>
:路由组件的占位符,App.vue 里写:<template> <div id="app"> <!-- 导航链接 --> <router-link to="/">首页</router-link> <router-link to="/about">lt;/router-link> <!-- 匹配到的路由组件会渲染到这里 --> <router-view></router-view> </div> </template>
-
<router-link>
:声明式导航。to
属性指定跳转路径,点击后自动处理路由切换,还能通过active-class
给激活的链接加样式(比如高亮)。
hash 模式 vs history 模式
路由模式决定 URL 长啥样,以及浏览器怎么处理跳转:
- hash 模式(默认):URL 带 ,
http://xxx.com/#/home
。 后面的变化不会触发浏览器请求,兼容性好,但 URL 不够美观。 - history 模式:URL 像普通网页,
http://xxx.com/home
,但需要服务器配合:刷新页面时,服务器得返回index.html
(否则会 404),适合追求 URL 美观的项目。
路由跳转除了 还有别的方式吗?
有!编程式导航适合在逻辑里(比如按钮点击后判断权限再跳转)控制路由,核心是 this.$router
上的方法:
常用方法:push / replace / go
-
this.$router.push(path | object)
:跳转到新路由,往历史记录里新增一条。methods: { goDetail() { // 方式1:传字符串路径 this.$router.push('/detail') // 方式2:传对象(配合 name + params/query) this.$router.push({ name: 'detail', params: { id: 123 } }) this.$router.push({ path: '/detail', query: { id: 123 } }) } }
-
this.$router.replace(path | object)
:和push
类似,但替换当前历史记录,比如从详情页跳编辑页,不想让用户回退到详情页时用它。 -
this.$router.go(n)
:控制历史记录前进/后退。n=1
前进一页,n=-1
后退一页(类似浏览器的前进后退按钮)。
params vs query 传参区别
传参是路由跳转的高频需求,但这俩方式差异很大:
- params 传参:参数“藏”在路由里(如
/detail/123
),需要路由配置成/detail/:id
才能用;history 模式下刷新会丢失参数(因为 URL 里没体现,刷新时服务器不认)。 - query 传参:参数“挂”在 URL 后(如
/detail?id=123
),不需要路由配动态参数,刷新后参数还在,适合传非敏感的临时数据。
name 跳转 vs path 跳转
路由规则里的 name
是路由的“唯一标识”,跳转时用 name
更灵活:
- 用
name
跳转时,可配合params
传参(如{ name: 'detail', params: { id: 123 } }
)。 - 用
path
跳转时,params
会被忽略,只能用query
传参(如{ path: '/detail', query: { id: 123 } }
)。
所以建议:如果路由配了 name
,优先用 name
跳转~
动态路由参数怎么玩?
做商品详情、用户信息这类“根据 ID 展示不同内容”的页面,得用动态路由匹配。
配置动态路由
在路由规则里,把路径写成带参数的形式(如 :id
):
{ path: '/product/:id', // :id 是动态参数 name: 'product', component: () => import('../views/ProductView.vue') }
组件内获取参数
在 ProductView.vue
里,通过 this.$route.params.id
拿到路由参数:
export default { created() { console.log(this.$route.params.id) // 路径是 /product/1001 时,输出 1001 } }
注意:$route
是当前路由的信息对象(存了路径、参数、元信息等);$router
是操作路由的实例(用来跳转、前进后退等),别搞混~
路由参数变化时组件不更新?
比如从 /product/1001
跳转到 /product/1002
,组件会被复用(因为组件相同),导致 created
/ mounted
这些钩子不重新执行,这时候得监听路由变化:
-
用 watch 监听
$route
:export default { watch: { '$route.params.id'(newId) { this.fetchData(newId) // 新 ID 来了,重新请求数据 } }, methods: { fetchData(id) { /* 调接口拿数据 */ } } }
-
用组件内守卫
beforeRouteUpdate
:export default { beforeRouteUpdate(to, from, next) { this.fetchData(to.params.id) next() // 必须调用 next() 才能继续跳转 } }
拓展:路由别名(alias)
如果想让 /p/123
也能访问到 /product/123
对应的组件,可给路由加 alias
:
{ path: '/product/:id', alias: '/p/:id', // 访问 /p/123 也会匹配到这个路由 component: ProductView }
实际项目中用得少,但遇到特殊 URL 需求时很方便~
嵌套路由该怎么配置?
做后台管理系统时,经常需要“公共布局 + 动态内容”(比如左侧侧边栏固定,中间内容随路由变),这就得用嵌套路由。
配置 children 数组
在父路由的规则里,加 children
数组放子路由规则:
{ path: '/admin', // 父路由路径 component: () => import('../layouts/AdminLayout.vue'), // 父组件(公共布局) children: [ { path: '', // 空路径 = 默认子路由,访问 /admin 时渲染它 name: 'dashboard', component: () => import('../views/AdminDashboard.vue') }, { path: 'users', // 子路由路径,完整路径是 /admin/users name: 'users', component: () => import('../views/AdminUsers.vue') }, { path: 'posts', name: 'posts', component: () => import('../views/AdminPosts.vue') } ] }
父组件里放 当子出口
AdminLayout.vue
得有个 <router-view>
来渲染子路由组件:
<template> <div class="admin-layout"> <aside>固定侧边栏</aside> <main> <!-- 子路由组件渲染在这里 --> <router-view></router-view> </main> </div> </template>
这样,访问 /admin
时,显示 AdminLayout + AdminDashboard
;访问 /admin/users
时,显示 AdminLayout + AdminUsers
,公共侧边栏始终存在~
路由守卫有啥用?怎么用?
路由守卫是“路由跳转的生命周期钩子”,能在跳转前、跳转后、进入组件前做权限判断、数据加载、离开提示等,分三类:
全局守卫:作用于所有路由
写在 router/index.js
的路由实例上,控制整个应用的路由逻辑:
-
router.beforeEach(to, from, next)
:跳转前触发,常用作权限控制,比如判断用户是否登录:router.beforeEach((to, from, next) => { const isLogin = localStorage.getItem('token') // 路由元信息:给需要权限的路由加 meta.requiresAuth if (to.meta.requiresAuth && !isLogin) { next({ name: 'login' }) // 没登录且需要权限,跳登录页 } else { next() // 放行 } })
-
router.afterEach(to, from)
:跳转后触发,常用作改页面标题、埋点统计,比如改标题:router.afterEach((to) => { document.title = to.meta.title || '默认标题' })
路由独享守卫:只作用于单个路由
直接写在某个路由规则里,只有访问该路由时触发:
{ path: '/order', name: 'order', component: OrderView, beforeEnter: (to, from, next) => { // 比如判断订单是否存在,不存在跳404 const orderExist = checkOrder(to.params.orderId) if (orderExist) { next() } else { next({ name: '404' }) } } }
组件内守卫:作用于当前组件
写在组件的选项里,针对当前组件的路由变化:
-
beforeRouteEnter(to, from, next)
:进入组件前触发,此时组件实例还没创建(this
是undefined
),如果要给组件传数据,用next(vm => {})
:export default { beforeRouteEnter(to, from, next) { // 获取数据后传给组件实例 fetchData(to.params.id).then(data => { next(vm => { vm.data = data // vm 是组件实例 }) }) } }
-
beforeRouteUpdate(to, from, next)
:组件复用时触发(比如路由参数变化,组件没销毁),前面动态路由部分讲过怎么用它处理数据更新。 -
beforeRouteLeave(to, from, next)
:离开组件前触发,常用作“是否保存未提交内容”的提示:export default { beforeRouteLeave(to, from, next) { if (this.formDirty) { // formDirty 标记表单是否修改 const confirm = window.confirm('有未保存内容,确定离开?') confirm ? next() : next(false) // 确认则放行,否则取消跳转 } else { next() } } }
路由懒加载怎么配置?能优化性能?
项目大了,把所有组件打包到一个文件里,首屏加载会很慢。路由懒加载能把不同路由的组件分成不同代码块,访问时再加载,减少首屏压力。
配置方式:动态 import
把原来的“静态导入组件”改成动态 import 形式:
原来的静态导入(所有组件打包到一起):
import HomeView from '../views/HomeView.vue' // 路由里写 component: HomeView
改成懒加载:
{ path: '/about', name: 'about', component: () => import('../views/AboutView.vue') }
这样,AboutView
会被单独打包成一个 chunk
,只有用户访问 /about
时才加载这个 chunk
。
优化:给 chunk 起名字
用 webpack 魔法注释(Vite 也支持类似配置)给 chunk 命名,方便调试:
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
打包后,这个 chunk 会叫 about.js
,而不是随机的哈希名~
懒加载的好处
- 首屏加载的 JS 文件体积变小,加载更快。
- 用户没访问的页面暂时不加载,节省流量和性能。
尤其适合页面多、组件大的项目,比如后台管理系统~
路由报错、页面不跳转这些坑怎么避?
用 Vue Router 时,一些“玄学”问题其实是配置或理解不到位导致的,列几个高频坑和解法:
路由配置了但页面不显示?
- 检查
<router-view>
是否漏写,App.vue 里有没有<router-view>
,嵌套路由的父组件里有没有给子路由留<router-view>
。 - 检查
routes
里的path
拼写(比如把/home
写成/hom
)。 - 若用
history
模式,检查服务器配置,刷新页面时,服务器得返回index.html
(否则会 404)。
重复点击路由报错?
Vue Router 3.x 版本中,重复点击同一个路由(比如点两次 <router-link to="/home">
)会抛错,解法:
- 升级到 Vue Router 4.x(兼容 Vue3),该问题已修复。
- 编程式导航时,判断当前路由是否和目标路由一致,一致则不跳转;或用
try...catch
吞掉错误:methods: { goHome() { try { this.$router.push('/home') } catch (error) { /* 吞掉错误 */ } } }
params 传参刷新后丢失?
history
模式下,params
是“隐式”传参(URL 里没体现),刷新时服务器不认,导致参数丢失,解法:
- 改用
query
传参(URL 里能看到,刷新还在)。 - 把参数存在 Vuex 或 localStorage,刷新后再取。
嵌套路由不渲染?
- 检查父路由的
children
配置:子路由的path
别加斜杠(比如父路由是/admin
,子路由path
写'users'
对应/admin/users
,加 会变成根路径,就错了)。 - 检查父组件里是否有
<router-view>
作为子路由出口。
把 Vue Router 拆成这些问题后,是不是觉得每个知识点都清晰多了?路由配置、跳转、传参、守卫这些核心功能,其实都是围绕“单页应用如何管理页面切换和状态”来设计的,建议你跟着文章里的例子,自己搭
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。