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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。