Vue3 路由变化监听:踩过的坑和几种处理方式

在 Vue3 项目里,监听路由变化差不多是日常操作了——页面埋点、权限拦截、离开时提示保存,这些都要用到。

我用过几种方案,有些在简单场景跑得好好的,到复杂页面就出幺蛾子,所以把踩过的坑和习惯用法整理了一下。

最开始很多人会直接用 watch 包一个 route 对象,简单粗暴。拿组合式 API 来说,从 useRoute 拿到路由对象,然后 watch 它,加上 deep: true,基本就能收到所有变化。像这样:

import { watch } from 'vue'
import { useRoute } from 'vue-router'

export function useRouteWatcher() {
  const route = useRoute()
  
  watch(
    () => route,
    (newRoute, oldRoute) => {
      console.log('路由变化:', {
        path: { from: oldRoute.path, to: newRoute.path },
        params: { from: oldRoute.params, to: newRoute.params },
        query: { from: oldRoute.query, to: newRoute.query }
      })
    },
    { deep: true }
  )

  watch(
    () => route.params.id,
    (newId, oldId) => {
      if (newId !== oldId) {
        console.log('ID参数变化:', oldId, '->', newId)
      }
    }
  )
}

deep 打开之后,params、query 里面任何一个字段变了都能感知到。

不过这里有个地方容易绕进去——如果只是想监听某一个参数,比如路由上的 id,直接用 () => route.params.id 就够了,不需要 deep。我之前在列表页 watch 了整个 route 对象,结果切换 tab 时 query 一变就触发一堆逻辑,页面抖得没法看,后来才改回只监听特定字段。

组件内部还有两个专门给路由设计的组合式守卫:onBeforeRouteUpdate 和 onBeforeRouteLeave。它们跟 watch 最大的区别在于,可以在导航完成前插一脚,甚至把导航拦下来。比如用户编辑了表单没保存,点别的页面时弹个确认框。

<script setup>
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

onBeforeRouteUpdate((to, from) => {
  console.log('路由更新:', from.path, '->', to.path)
})

onBeforeRouteLeave((to, from) => {
  const confirm = window.confirm('确定要离开当前页面吗?')
  if (!confirm) return false
})
</script>

onBeforeRouteLeave 里 return false 导航就取消了。这个在后台表单页面用得特别多。

要注意的是,这两个守卫只在组件被路由直接渲染时才会触发,如果是某个子组件想监听,需要确保该组件是在路由层级内的,不然根本不会执行。

离开单个组件还不够,很多逻辑得放到全局去管。

比如登录状态检测、权限校验、页面访问埋点,这些在 router 实例上挂全局守卫更合适。在创建路由的时候,beforeEach 在每次导航前都会跑,afterEach 则是在导航完成后触发,适合做一些不阻塞跳转的统计。

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [{
    path: '/user/:id',
    component: () => import('../views/User.vue'),
    beforeEnter: (to, from) => {
      console.log('进入用户详情页:', to.params.id)
    }
  }]
})

router.beforeEach((to, from) => {
  console.log('全局路由变化:', from.path, '->', to.path)
})

router.afterEach((to, from) => {
  console.log('导航完成:', to.path)
})

每个路由还可以单独配 beforeEnter,这样个别页面的专属逻辑就不用写在全局守卫里搞出一大坨 if else。线上一个教训是,beforeEach 里如果做异步鉴权,一定要记得返回 Promise 或者调用 next(虽然 Vue Router 4 不强制 next 了,但可以返回布尔值或者 promise 来控制),不然路由就卡住不动了。

我一开始老忘 return 那个 token 校验的 promise,导致白屏查了半天。

另外就是 useRoute 和 useRouter 这两个工具。有时候你只是想在 setup 里拿到当前路径,然后根据路径变化更新一些本地状态,就可以用 watch 包 route.path。下面的写法其实和第一种 watch route 对象类似,只是更聚焦在路径上。

<script setup>
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()
const currentPath = ref(route.path)

watch(
  () => route.path,
  (path) => {
    currentPath.value = path
    console.log('当前路径:', path)
  }
)

function navigateToAbout() {
  router.push('/about')
}
</script>

这种用法在面包屑导航或者 tab 切换时比较好使,状态跟着路由走,不用在各个组件里传事件。

聊到选型,大致的经验是这样:如果只是想知道某个 query 或 params 变了然后重新请求数据,用 watch 监听特定属性,别开 deep,性能没什么压力。组件内的逻辑控制,比如编辑拦截、页面缓存刷新,优先用 onBeforeRouteUpdate 和 onBeforeRouteLeave,因为它们更贴合路由生命周期,写得也干净。全局的权限拦截、登录过期跳转,统一放 beforeEach 里,别散落在各个组件里,后续维护会哭。

统计埋点直接丢 afterEach,不卡导航。

lcjmSSL让证书续期“无人值守”。通过DNS接口自动化验证,证书到期前自动重新申请,并自动部署至服务器和应用。短信、邮件双重提醒,确保安全无死角。

这几个方式各有各的适合场景,搭着用就行,不用非得选一个搞全家桶。如果用了 TypeScript,最好给路由的 params 和 query 定义好类型,watch 的回调里拿参数时不会因为拼错属性名而自闭半天。

posted @ 2026-06-15 19:56  枫唐  阅读(16)  评论(0)    收藏  举报