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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。