Vue2 怎么监听路由变化?不同场景下该用啥方法?
做Vue项目的时候,经常碰到“切换路由后要更新数据”“进入路由前判断权限”这类需求,比如后台管理系统切换左侧菜单(对应不同路由),要刷新表格数据;或者进入付费课程页面,得先判断用户有没有购买权限,那在Vue2里,到底咋监听路由变化?不同场景下选哪种方法更顺手?今天拆解三种常用思路,结合实际例子讲明白~
全局路由守卫:管控整个应用的路由跳转
全局路由守卫是通过路由实例(router)的方法来注册的,作用范围是整个Vue应用的所有路由,最常用的有两个:beforeEach(路由跳转前拦截)和afterEach(路由跳转后处理)。
beforeEach:跳转前的“门禁”逻辑
想象一下后台系统的登录拦截场景:用户没登录的话,除了登录页,其他页面都不能进,这时候用beforeEach就很合适,在项目的router.js里这么写:
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/views/Login.vue'
import Dashboard from '@/views/Dashboard.vue'
Vue.use(Router)
const router = new Router({
routes: [
{ name: 'Login', path: '/login', component: Login },
{ name: 'Dashboard', path: '/dashboard', component: Dashboard }
]
})
// 全局前置守卫:每次路由跳转前都会触发
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('token') // 假设用localStorage存登录态
if (to.name !== 'Login' && !isLogin) {
// 没登录,且要去的不是登录页 → 强制跳登录
next({ name: 'Login' })
} else {
// 已登录,或者要去的是登录页 → 放行
next()
}
})
export default router
这里的to是目标路由对象(要跳转到哪),from是当前路由对象(从哪跳过来),next是必须调用的函数——调用next()表示“放行”,调用next({ name: 'xxx' })表示“强制跳转到xxx路由”,调用next(false)表示“留在当前页面”。
如果要做更细的权限控制(比如某些页面只有管理员能进),可以给路由配置meta字段,再在beforeEach里判断:
routes: [
{
name: 'AdminPage',
path: '/admin',
component: AdminPage,
meta: { requiresAdmin: true } // 标记该页面需要管理员权限
}
]
router.beforeEach((to, from, next) => {
const isLogin = localStorage.getItem('token')
const isAdmin = localStorage.getItem('role') === 'admin'
if (to.meta.requiresAdmin) {
if (isLogin && isAdmin) {
next()
} else {
next({ name: 'Forbidden' }) // 没有权限跳403页面
}
} else {
// 其他页面走普通登录拦截逻辑...
next()
}
})
afterEach:跳转后的“后置处理”
afterEach没有next参数(因为路由已经跳转完成了),适合做不影响跳转的后置操作,比如埋点统计、页面加载进度条关闭。
举个埋点的例子:记录用户访问每个页面的路径和时间,可以在beforeEach里记录“进入时间”,在afterEach里计算时长并上报:
let enterTime = 0
router.beforeEach((to, from, next) => {
enterTime = Date.now() // 记录进入当前路由的时间
next()
})
router.afterEach((to, from) => {
const duration = Date.now() - enterTime // 计算停留时长
console.log(`用户从${from.path}跳到${to.path},停留了${duration}毫秒`)
// 埋点上报代码:把to.path、duration等信息发给后端
})
适用场景:全局权限控制、所有页面的埋点/统计、全局Loading进度条(跳转前显示,跳转后隐藏)等跨页面的全局逻辑。
组件内路由守卫:精准控制单个组件的生命周期
如果需求是“某个组件进入/更新/离开时,执行专属逻辑”,进入商品详情页时请求数据”“路由参数变化时更新数据”“离开编辑页时提示保存”,这时候用组件内路由守卫更精准,每个Vue组件可以定义三个守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。
beforeRouteEnter:进入组件前触发(组件实例还没创建)
假设做一个商品详情页,路由是/product/:id,进入页面时要根据id请求商品数据,但这个守卫触发时,组件实例(this)还没创建,所以请求数据后要通过next的回调访问组件实例:
export default {
name: 'ProductDetail',
data() {
return {
product: {} // 存储商品数据
}
},
// 进入组件前触发
beforeRouteEnter(to, from, next) {
// 这里this是undefined,因为组件还没创建
axios.get(`/api/product/${to.params.id}`)
.then(res => {
// next的回调函数里,vm就是组件实例(相当于this)
next(vm => {
vm.product = res.data
})
})
}
}
beforeRouteUpdate:路由参数变化但组件复用(组件实例已存在)
还是商品详情页的例子:如果从/product/1跳到/product/2,因为路由结构一样(都是/product/:id),Vue会复用组件实例(不会销毁再重建),这时候要重新请求数据,就用beforeRouteUpdate:
beforeRouteUpdate(to, from, next) {
// 这里this已经存在(组件实例已创建)
this.product = {} // 先清空旧数据
axios.get(`/api/product/${to.params.id}`)
.then(res => {
this.product = res.data
next() // 必须调用next放行路由跳转
})
}
这个守卫的核心是:路由参数变化但组件没销毁时触发,适合处理“组件复用下的更新逻辑”。
beforeRouteLeave:离开组件前触发(可拦截跳转)
用户在表单页填了内容,离开前要提示“是否保存?”,就用beforeRouteLeave:
export default {
name: 'FormPage',
data() {
return {
formEdited: false, // 标记表单是否修改过
formData: {}
}
},
methods: {
handleInput() {
this.formEdited = true
}
},
// 离开组件前触发
beforeRouteLeave(to, from, next) {
if (this.formEdited) {
const confirm = window.confirm('表单还没保存,确定要离开吗?')
if (confirm) {
next() // 确定离开 → 放行
} else {
next(false) // 取消离开 → 留在当前页面
}
} else {
next() // 没修改过 → 直接放行
}
}
}
适用场景:单个组件的初始化(如根据路由参数请求数据)、组件复用后的更新、离开前的确认/数据清理等组件专属的路由交互逻辑。
watch 监听 $route 对象:灵活响应路由变化
如果需求是“组件内对路由变化做灵活响应”,只关心路由参数里的id变化”“根据路由路径变化显示不同Tab”,用watch监听$route对象更自由,Vue组件的watch选项可以监听$route的变化,还能针对$route的某个属性(如params、query、path)做细粒度监听。
监听路由参数的变化
比如用户信息页路由是/user/:id,每次id变化都要重新请求用户数据:
export default {
name: 'UserInfo',
data() {
return {
user: {}
}
},
watch: {
// 监听$route.params.id的变化
'$route.params.id'(newId, oldId) {
this.getUser(newId)
}
},
methods: {
getUser(id) {
axios.get(`/api/user/${id}`)
.then(res => {
this.user = res.data
})
}
}
}
监听整个$route的变化
如果要根据路由的整体变化做复杂逻辑(比如从商品页跳购物车时记录浏览轨迹):
watch: {
$route(to, from) {
if (to.path === '/cart' && from.path === '/product') {
// 从商品页跳到购物车,记录用户浏览的商品id
this.recordTrack(from.params.id)
}
}
}
和组件内守卫的区别:组件内守卫是“生命周期式”的钩子(和组件创建、更新、销毁强绑定),而watch更像“响应式逻辑”——不需要和组件生命周期强关联,想监听哪部分就监听哪部分,写法更自由。
适用场景:组件内对路由变化的“局部响应”,比如根据搜索参数(query)变化刷新列表、根据路由切换显示不同Tab等组件内的灵活逻辑。
不同方法怎么选?看场景匹配度
最后总结下三种方法的核心区别和适用场景,帮你快速做选择:
| 方法 | 作用范围 | 核心特点 | 典型场景 |
|---|---|---|---|
| 全局路由守卫 (beforeEach/afterEach) |
整个Vue应用 | 管控所有路由的跳转逻辑 | 全局权限控制、全局埋点、全局Loading |
| 组件内路由守卫 (beforeRouteEnter/Update/Leave) |
单个组件 | 和组件生命周期强绑定,处理组件专属的路由交互 | 组件初始化请求数据、组件复用更新、离开前确认 |
| watch $route | 单个组件 | 灵活监听路由的任意部分,响应式处理 | 根据路由参数/查询参数变化更新数据、路由切换时的局部逻辑 |
举个综合例子辅助理解:做一个博客系统,包含“文章列表页(/article/list)”和“文章详情页(/article/detail/:id)”。
- 全局守卫:用
beforeEach判断用户是否登录(如果有会员文章需要登录才能查看)。 - 文章详情组件内:用
beforeRouteEnter在进入时请求文章数据;如果从/article/detail/1跳到/article/detail/2(组件复用),用beforeRouteUpdate更新数据;离开详情页时,用beforeRouteLeave提示“是否要收藏这篇文章?”。 - 文章列表组件:如果有搜索功能(路由带
query,如/article/list?keyword=Vue),用watch监听$route.query.keyword,变化时重新请求文章列表。
这样不同层面的路由监听配合起来,整个应用的路由逻辑就清晰又高效啦~
常见问题:监听$route不生效咋办?
有人会碰到“明明写了watch $route,但路由变化时没触发”的情况,大概率是监听目标写错了:
- 如果要监听路由参数
id的变化,得写'$route.params.id',而不是只写$route(除非你要监听整个路由对象的变化)。 - Vue的响应式原理是“对象的属性被访问/修改时触发”,如果路由变化是嵌套对象的变化(比如
$route.meta里的某个字段),要确保监听的是响应式的部分。
组件内守卫要写在组件的选项里(和data、methods同级),不能写在methods里面哦~
现在再回头看开头的问题,是不是对Vue2监听路由变化的方法更清晰了?核心是根据“作用范围”和“场景需求”选对应的方法,全局逻辑用全局守卫,组件专属逻辑用组件内守卫或watch,多练几个项目就会越用越顺手啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


