Code前端首页关于Code前端联系我们

Vue2 怎么监听路由变化?不同场景下该用啥方法?

terry 12小时前 阅读数 12 #Vue
文章标签 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组件可以定义三个守卫:beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

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的某个属性(如paramsquerypath)做细粒度监听。

监听路由参数的变化

比如用户信息页路由是/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里的某个字段),要确保监听的是响应式的部分。

组件内守卫要写在组件的选项里(和datamethods同级),不能写在methods里面哦~

现在再回头看开头的问题,是不是对Vue2监听路由变化的方法更清晰了?核心是根据“作用范围”和“场景需求”选对应的方法,全局逻辑用全局守卫,组件专属逻辑用组件内守卫或watch,多练几个项目就会越用越顺手啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门