Vue Router实战中那些你必须掌握的路由控制技巧
我的写法,亲测靠谱
用 Vue Router 也快五年了,从 2.x 到 3.x 再到现在的 4.x(配合 Vue 3),踩过的坑能填满半个会议室。说实话,Vue Router 本身不难,但很多团队一上来就乱写,路由配置像意大利面条一样缠在一起,后期维护简直噩梦。我现在的项目里,路由这块基本稳定下来了,分享下我目前的写法。
首先,别把所有路由都塞在一个文件里。很多人图省事,把几十个页面全堆在 router/index.js 里,看起来是方便了,但一旦要拆权限、做懒加载、或者改结构,就得硬着头皮翻几百行代码。我的做法是:按模块拆分路由文件,然后动态导入合并。
// router/modules/user.js
export default [
{
path: '/user/profile',
name: 'UserProfile',
component: () => import('@/views/user/Profile.vue')
},
{
path: '/user/settings',
name: 'UserSettings',
component: () => import('@/views/user/Settings.vue')
}
]
// router/modules/admin.js
export default [
{
path: '/admin/dashboard',
name: 'AdminDashboard',
component: () => import('@/views/admin/Dashboard.vue'),
meta: { requiresAdmin: true }
}
]
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import userRoutes from './modules/user'
import adminRoutes from './modules/admin'
const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: () => import('@/views/Home.vue') },
...userRoutes,
...adminRoutes,
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/404.vue') }
]
const router = createRouter({
history: createWebHistory(),
routes
})
这种写法的好处很明显:谁负责哪个模块,就只动自己的路由文件;合并逻辑清晰,不会互相干扰;而且天然支持按需加载,首屏性能也好。
这几种错误写法,别再踩坑了
下面这些是我见过最多、也最容易出问题的写法,新手老手都容易栽进去。
1. 在路由守卫里直接跳转而不 return
很多人写全局前置守卫,验证登录状态,发现没登录就 router.push('/login'),但忘了 return next(false) 或者直接 return。结果就是:页面跳转了,但原来的导航还在继续执行,可能触发组件里的请求,甚至导致白屏或报错。
// 错误写法 ❌
router.beforeEach((to, from, next) => {
if (!isAuthenticated && to.meta.requiresAuth) {
router.push('/login') // 这里没 return!
}
next() // 还会继续走!
})
// 正确写法 ✅
router.beforeEach((to, from, next) => {
if (!isAuthenticated && to.meta.requiresAuth) {
next('/login') // 用 next 跳转,并终止当前导航
return
}
next()
})
2. 动态添加路由后不处理重复注册
有些项目要做菜单动态生成,会在登录后调接口拿到路由表,然后用 router.addRoute() 添加。但如果你不做去重判断,用户刷新页面后再登录,就会重复添加,导致同一个路径匹配多个组件,控制台疯狂报 warning,严重时页面直接挂掉。
我的处理方式是在添加前先清掉已有的动态路由(比如打个标记),或者干脆每次登录前重置路由:
// 登录成功后
function resetDynamicRoutes(routesFromServer) {
const dynamicRouteNames = router.getRoutes().map(r => r.name)
dynamicRouteNames.forEach(name => {
if (name && typeof name === 'string' && name.startsWith('dynamic-')) {
router.removeRoute(name)
}
})
routesFromServer.forEach(routeConfig => {
router.addRoute(routeConfig)
})
}
3. 用 params 传参却不配占位符
这个坑我踩过好几次。比如想通过 /user/:id 访问用户页,但在路由定义里写成 path: '/user',然后在跳转时用 router.push({ name: 'User', params: { id: 123 } })。结果就是参数根本传不过去,因为 path 里没有 :id 占位符,Vue Router 直接忽略 params。
记住:params 只对 path 中有对应参数占位符的路由生效。如果只是临时传参,建议用 query。
实际项目中的坑
除了上面那些典型错误,实际开发中还有一些细节特别容易忽略。
滚动行为别硬写死。很多人为了“回到顶部”,在 createRouter 里直接写:
scrollBehavior(to, from, savedPosition) {
return { top: 0 }
}
看起来没问题,但如果用户是从详情页点浏览器返回,期望回到列表页的上次滚动位置,这个写法就破坏了体验。我的做法是结合 savedPosition:
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
404 页面的写法也有讲究。很多人把通配路由写成 path: '*',这在 Vue Router 3 是对的,但在 Vue Router 4 里已经废弃了,必须用 /:pathMatch(.*)*。否则你访问不存在的路径,根本不会匹配到 404 组件。
还有,别在路由组件里做重度初始化逻辑。比如在 mounted 里发请求、初始化地图等。因为 Vue Router 默认会复用组件(比如从 /user/1 切到 /user/2,组件实例不变),这时候 mounted 不会重新执行,数据就错了。正确的做法是监听 $route 变化,或者用 watch:
export default {
watch: {
'$route'(to, from) {
if (to.params.id !== from.params.id) {
this.fetchUserData(to.params.id)
}
}
},
created() {
this.fetchUserData(this.$route.params.id)
}
}
或者更现代一点,用组合式 API:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
const userData = ref(null)
const fetchUserData = async (id) => {
userData.value = await fetch(https://jztheme.com/api/user/${id}).then(r => r.json())
}
watch(() => route.params.id, (newId) => {
fetchUserData(newId)
}, { immediate: true })
}
}
懒加载和预加载的平衡
很多人以为用了 () => import(...) 就万事大吉,其实不然。如果首页依赖太多子路由,首次加载还是慢。我的策略是:
- 首页相关组件同步引入(牺牲一点打包体积,换首屏速度)
- 非核心页面全部懒加载
- 关键路径(比如用户大概率会点的下一步)做预加载,比如在 hover 或 mousedown 时提前加载 chunk
预加载可以这样搞:
const Profile = () => import('@/views/user/Profile.vue')
// 在父组件里
onMounted(() => {
// 预加载
Profile()
})
虽然会多发一个请求,但用户体验提升明显,尤其是网络差的时候。
以上是我这几年用 Vue Router 总结的一些实战经验,有些方案不是最优雅的,但胜在稳定、易维护。毕竟项目上线后没人关心你代码多漂亮,只关心别崩。
以上是我个人对 Vue Router 的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论