学完这篇,你能独立搭起一套 Vue Router 4 的路由骨架,并把守卫鉴权、路由懒加载、参数传递这三件最常出问题的事做对。
第一步:安装与最小可跑的路由
npm install vue-router@4
新建 src/router/index.js:
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', name: 'home', component: () => import('../views/Home.vue') },
{ path: '/post/:id', name: 'post', component: () => import('../views/Post.vue') }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
在 main.js 里 app.use(router),然后在 App.vue 放上 <router-view /> 和 <router-link to="/">首页</router-link>,就能跑了。
注意:Vue Router 4 只配 Vue 3。如果你装成了 vue-router@3,createRouter 会报不存在。另外 mode: 'history' 这种 v3 写法在 v4 已经换成 history: createWebHistory(),别混着用。
第二步:路由懒加载怎么写才有效
上面 component: () => import(...) 就是懒加载,打包时每个页面切成独立 chunk,首屏只下当前页的代码。想给 chunk 起名方便排查:
component: () => import(/* webpackChunkName: "post" */ '../views/Post.vue')
如果是 Vite,用:
component: () => import('../views/Post.vue')
Vite 会自动按文件切分,不需要魔法注释。
注意:不要把懒加载写成 component: import('../views/Post.vue') 直接传 Promise,那不是路由级别的懒加载,容易因为组件异步返回时序出问题。必须是返回 Promise 的函数。还有,首页、登录页这类必进页面可以不做懒加载,反而更快。
第三步:三种传参方式,选对场景
1. params(路径参数) —— 用于标识资源,如 /post/123:
// 定义
{ path: '/post/:id', name: 'post', component: Post, props: true }
// 跳转
router.push({ name: 'post', params: { id: 123 } })
// 组件内
const props = defineProps({ id: String })
2. query(查询参数) —— 用于筛选、分页,如 /list?page=2&tag=vue:
router.push({ path: '/list', query: { page: 2, tag: 'vue' } })
// 组件内
const route = useRoute()
console.log(route.query.page)
3. props 解耦 —— 让组件不依赖 useRoute(),好测试、好复用。除了 props: true,还能传对象或函数:
{ path: '/user/:id', component: User, props: route => ({ id: route.params.id, tab: route.query.tab }) }
注意:v4 里 params 只在有 name 的命名路由跳转时传递才可靠,用 path 跳转时 params 会被丢弃。另外 query 的值永远是字符串,比较数字要自己 Number() 转换。
第四步:路由守卫的四个层级
全局守卫(写在 router 实例上):
router.beforeEach((to, from) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
return { name: 'login', query: { redirect: to.fullPath } }
}
// 什么都不返回 = 放行
})
router.afterEach((to, from, failure) => {
if (!failure) document.title = to.meta.title || '默认标题'
})
路由独享守卫:
{ path: '/admin', component: Admin, beforeEnter: (to, from) => { /* 返回 false 取消 */ } }
组件内守卫(<script setup> 里用 onBeforeRouteLeave、onBeforeRouteUpdate)。
守卫返回值规则:返回 false 取消导航;返回路由地址则重定向;返回 undefined 或 true 放行。
注意:beforeEach 里不要写 next() 和 return 混用,v4 支持返回值写法后,两种风格混着容易死循环或导航卡死。选一种,全项目统一。异步鉴权接口记得 await,否则守卫会在接口回来前就放行。
第五步:参数变化但组件复用怎么办
从 /post/1 跳到 /post/2,路由是同一个组件,onMounted 不会重新触发,页面数据不刷新。三种解法:
// 方案一:监听参数
watch(() => route.params.id, (id) => fetchPost(id), { immediate: true })
// 方案二:组件内守卫
onBeforeRouteUpdate((to) => { fetchPost(to.params.id) })
// 方案三:粗暴但有效——让路由不复用
<router-view :key="route.fullPath" />
注意:方案三会丢失页面滚动位置和组件内部状态,只在确实需要整页重建时用。
第六步:几个常见坑位收尾
- 404 兜底:
{ path: '/:pathMatch(.*)*', component: NotFound },v3 的 '*' 在 v4 已废弃。
- 滚动行为:
scrollBehavior(to, from, savedPosition) { return savedPosition || { top: 0 } },否则返回上一页位置全丢。
- Nginx/Apache 部署:history 模式必须把 404 回退到
index.html,否则刷新就白屏。
小结
- 用
createRouter + createWebHistory 起步,别混用 v3 写法。
- 懒加载必须是「返回 Promise 的函数」,首屏页面可例外。
- params 走命名路由,query 适合筛选分页,props 解耦组件。
- 守卫统一用一种写法(返回值或
next()),异步鉴权要 await。
- 同组件参数变化不会重新挂载,用
watch 或 onBeforeRouteUpdate 处理。
- 记得写 404 兜底和服务器 history 回退配置。