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

Vue Router 怎么获取上一个路由?previous route 实用技巧全解析

terry 14小时前 阅读数 13 #Vue

做 Vue 项目时,经常遇到要“知道用户从哪个页面跳过来”的需求——比如顶部导航的返回按钮要精准回退、统计用户路径的埋点、甚至某些页面必须从指定页面跳转才能进入……这时候就得和 Vue Router 的 previous route 打交道,可怎么优雅地拿到上一个路由?不同场景怎么用?踩过哪些坑?今天一次性聊透~

基础:Vue Router 里怎么拿到 previous route?

先明确核心逻辑:路由切换时,“上一个路由”是相对当前跳转动作而言的,所以要在“路由变化的过程中”去捕获旧路由,Vue Router 提供了路由守卫、组件内路由监听这两类关键入口,下面分方法拆解:

全局路由守卫:router.beforeEach 记录来源

全局守卫是“拦截所有路由跳转”的入口,每次路由变化前都会触发 router.beforeEach,参数里的 from 上一个路由”,to 是目标路由。

举个实际代码结构(以 Vue3 + Vue Router4 为例,Vue2 逻辑类似):

在路由配置文件(router/index.js)里:

import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'
const router = createRouter({
  history: createWebHistory(),
  routes
})
// 全局守卫:记录上一个路由到 Pinia/Vuex
import { useRouteStore } from '@/stores/route'
router.beforeEach((to, from) => {
  const routeStore = useRouteStore()
  routeStore.setPrevRoute(from) // 把 from 存到状态管理里
})
export default router

然后在任意组件里,从 Pinia 取数据:

import { useRouteStore } from '@/stores/route'
export default {
  setup() {
    const routeStore = useRouteStore()
    console.log(routeStore.prevRoute) // 上一个路由对象
  }
}

⚠️ 注意:首次进入应用时,fromundefined(因为没有“上一个路由”),所以业务逻辑里要判断 from?.name 是否存在,避免报错。

组件内 watch $route:局部捕获路由变化

如果只想在某个组件内关注路由变化,不需要全局存储,可以用组件内的 watch 监听 $route(Vue2)或 useRoute(Vue3)。

  • Vue2 写法
    在组件的 watch 选项里监听 $route,参数 to 是新路由,from 是旧路由:

    export default {
      data() {
        return {
          prevRoute: null
        }
      },
      watch: {
        $route(to, from) {
          this.prevRoute = from
        }
      }
    }
  • Vue3 写法(Composition API):
    watch 监听 useRoute() 返回的路由对象,同样能拿到 from

    import { watch, ref } from 'vue'
    import { useRoute } from 'vue-router'
    export default {
      setup() {
        const route = useRoute()
        const prevRoute = ref(null)
        watch(route, (to, from) => {
          prevRoute.value = from
        })
        return { prevRoute }
      }
    }

⚠️ 细节:组件初始化时,watch 还没执行,prevRoute 初始是 null,如果需要页面加载后立刻拿到“上一个路由”(比如页面刷新后),得结合全局存储或浏览器缓存(后面讲坑的时候会展开)。

路由元信息(meta)的“反向标记”思路

我们不只是“被动获取上一个路由”,还可以主动标记哪些页面是重要来源,比如在路由配置的 meta 里加一个 isSource 标记:

const routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home,
    meta: { isSource: true } // 标记为“可作为来源的页面”
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

然后在目标页面的守卫里,判断 from.meta.isSource 是否为 true,来决定逻辑,这种“主动标记”+“来源筛选”的组合,能解决“只有特定页面跳转过来才生效”的场景(比如权限控制)。

获取 previous route 有哪些实际场景?

知道方法后,得明白“为什么要做”——这些场景能帮你理解需求价值:

导航栏回退逻辑(模拟原生返回)

移动端 H5 页面里,顶部往往有个“返回”按钮,但浏览器默认的 history.back() 可能跳转到非应用内页面(比如用户从微信打开应用,返回会跳转到微信),这时候需要精准回退到应用内的上一个页面

// 组件内逻辑(以 Vue3 为例)
import { useRouter } from 'vue-router'
import { useRouteStore } from '@/stores/route'
export default {
  setup() {
    const router = useRouter()
    const routeStore = useRouteStore()
    const goBack = () => {
      if (routeStore.prevRoute) {
        router.push(routeStore.prevRoute.path)
      } else {
        // 没有上一个路由,跳转到首页
        router.push('/home')
      }
    }
    return { goBack }
  }
}

页面上的按钮绑定 goBack,就能实现“应用内返回”,比浏览器默认返回更可控。

埋点统计:追踪用户路径

运营同学需要知道“用户从哪个页面来,到哪个页面去”,分析流量转化,用全局守卫埋点很高效:

router.beforeEach((to, from) => {
  if (from.name) { // 排除首次进入
    // 发送埋点数据到后端
    fetch('/api/track', {
      method: 'POST',
      body: JSON.stringify({
        from: from.path,
        to: to.path,
        time: Date.now()
      })
    })
  }
})

这样所有页面跳转都会被记录,后续分析“哪个页面引流最多”“用户流失在哪个环节”就有了数据基础。

页面级权限:限制跳转来源

某些敏感页面(比如订单支付页),必须从“订单确认页”跳转过来,否则禁止访问,这时候用 from 做权限判断:

router.beforeEach((to, from) => {
  if (to.name === 'Pay' && from.name !== 'OrderConfirm') {
    // 不是从订单确认页来的,跳转到错误页
    return { name: 'Error', params: { code: 403 } }
  }
})

类似的逻辑还能用于“营销活动页只能从首页跳转”“内部页面禁止外部链接直接进入”等场景。

页面切换动画:根据来源决定方向

做 SPA 切换动画时,经常需要“从 A 页面来就从左滑入,从 B 页面来就从右滑入”,这时候 previous route 就是动画方向的判断依据:

// 假设用 Vue Transition + 动态 class
<template>
  <transition :name="transitionName">
    <router-view />
  </transition>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const transitionName = ref('slide-left')
watch(route, (to, from) => {
  if (from.name === 'Home') {
    transitionName.value = 'slide-right'
  } else {
    transitionName.value = 'slide-left'
  }
})
</script>
<style>
.slide-left-enter-active, .slide-left-leave-active {
  transition: all 0.3s;
}
.slide-left-enter-from {
  transform: translateX(100%);
}
.slide-left-leave-to {
  transform: translateX(-100%);
}
/* 右侧滑入同理 */
</style>

通过 from.name 判断动画方向,让页面切换更丝滑自然。

实现过程中容易踩的坑是什么?

方法用错场景,很容易掉坑里,这些“反直觉”的细节要注意:

首次进入时,previous route 是 undefined

应用第一次加载时,没有“上一个路由”,fromundefined,如果直接访问 from.path 会报错,必须做容错:

// 全局守卫里的安全写法
router.beforeEach((to, from) => {
  if (from) { // 或者 from?.name
    // 处理上一个路由
  }
})
// 组件内的安全写法
watch(route, (to, from) => {
  if (from) {
    prevRoute.value = from
  }
})

异步路由加载时,守卫执行时机

如果用了异步路由component: () => import('./views/About.vue')),路由加载是异步的,这时候 beforeEach 的执行时机要注意:

  • 异步路由加载时,beforeEach 会先执行,等组件加载完才会跳转,所以如果在守卫里依赖“目标组件已加载”的逻辑,可能不生效。
  • 解决方案:如果要等组件加载完再处理,用 afterEach(但 afterEach 没有 next,只能做后置操作),或者在组件内的 onMounted 里处理。

多标签页/新窗口打开时,路由记忆失效

Vue Router 是单页应用(SPA),每个标签页/新窗口都是独立的 Vue 实例,路由记录不共享,比如用户右键“在新标签页打开”,新页面的 previous routeundefined,因为它是全新的会话。

怎么兼容?可以结合浏览器存储(sessionStorage/localStorage)传递路由信息:

// 全局守卫:存上一个路由到 sessionStorage
router.beforeEach((to, from) => {
  if (from.name) {
    sessionStorage.setItem('prevRoute', JSON.stringify(from))
  }
})
// 页面加载时,从 sessionStorage 恢复
import { onMounted } from 'vue'
import { useRouteStore } from '@/stores/route'
export default {
  setup() {
    const routeStore = useRouteStore()
    onMounted(() => {
      const storedPrev = sessionStorage.getItem('prevRoute')
      if (storedPrev) {
        routeStore.setPrevRoute(JSON.parse(storedPrev))
      }
    })
  }
}

但要注意:sessionStorage同标签页共享的,不同标签页还是独立的,所以这种方案只能解决“刷新页面”时的路由记忆,新标签页的场景只能做有限兼容。

同一路由不同参数,是否算“新的 previous”?

比如路由是 /user/:id,从 /user/1 跳到 /user/2,这时候 from/user/1to/user/2——这属于“同一路由不同参数”,from 依然是有效的上一个路由。

但如果业务逻辑里把“同路由不同参数”视为“没跳转”,就需要额外判断 from.pathto.path 是否相同,再结合 params 变化:

watch(route, (to, from) => {
  if (from.path !== to.path) {
    // 路径不同,算新的跳转
    prevRoute.value = from
  } else {
    // 路径相同,参数变化,根据业务决定是否更新 prevRoute
  }
})

有没有更灵活的封装方式?

重复写“存上一个路由”的逻辑很冗余,封装成工具能提高效率,推荐两种思路:

Vue2:封装插件自动记录

Vue2 可以写一个插件,全局注入 $prevRoute

// plugins/prevRoute.js
export default {
  install(Vue, router) {
    let prevRoute = null
    router.beforeEach((to, from) => {
      prevRoute = from
    })
    Vue.prototype.$prevRoute = () => prevRoute
  }
}
// main.js 里注册
import PrevRoutePlugin from './plugins/prevRoute'
Vue.use(PrevRoutePlugin, router)
// 组件内使用
export default {
  mounted() {
    console.log(this.$prevRoute()) // 上一个路由
  }
}

这样所有组件都能通过 this.$prevRoute() 拿到值,不用重复写 watch 或守卫。

Vue3:组合式函数(Hook)复用逻辑

Vue3 的 Composition API 适合封装 Hook,比如写一个 usePreviousRoute

// hooks/usePreviousRoute.js
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
export function usePreviousRoute() {
  const route = useRoute()
  const prevRoute = ref(null)
  watch(route, (to, from) => {
    prevRoute.value = from
  }, { immediate: false }) // immediate 设为 false,避免首次执行时 from 是当前路由
  return { prevRoute }
}
// 组件内使用
import { usePreviousRoute } from '@/hooks/usePreviousRoute'
export default {
  setup() {
    const { prevRoute } = usePreviousRoute()
    console.log(prevRoute.value)
    return { prevRoute }
  }
}

Hook 里封装了 watch 逻辑,组件只需引入调用,代码更简洁。

结合 Pinia/Vuex 做全局状态管理

如果多个组件需要共享 previous route,用状态管理更高效,以 Pinia 为例:

// stores/route.js
import { defineStore } from 'pinia'
export const useRouteStore = defineStore('route', {
  state: () => ({
    prevRoute: null
  }),
  actions: {
    setPrevRoute(route) {
      this.prevRoute = route
    }
  }
})
// 全局守卫里调用
import { useRouteStore } from '@/stores/route'
router.beforeEach((to, from) => {
  const routeStore = useRouteStore()
  routeStore.setPrevRoute(from)
})
// 组件内使用
import { useRouteStore } from '@/stores/route'
export default {
  setup() {
    const routeStore = useRouteStore()
    console.log(routeStore.prevRoute)
  }
}

状态管理确保了 previous route 在全局的一致性,适合复杂项目。

Vue3 + Vue Router4 有哪些新变化?

Vue3 和 Vue Router4(也叫 Vue Router Next)相比 Vue2 有不少语法和逻辑变化,获取 previous route 时要注意:

路由创建方式:createRouter + createWebHistory

Vue Router4 不再用 new VueRouter(),而是用 createRouter 创建实例,历史模式用 createWebHistory(对应 Vue2 的 mode: 'history'):

import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(),
  routes
})

Composition API:useRoute + useRouter

Vue3 的组合式 API 里,用 useRoute 获取当前路由对象,useRouter 获取路由实例,替代了 Vue2 的 this.$routethis.$router

import { useRoute, useRouter } from 'vue-router'
export default {
  setup() {
    const route = useRoute() // 对应 this.$route
    const router = useRouter() // 对应 this.$router
    // 监听路由变化
    watch(route, (to, from) => {
      // ...
    })
  }
}

守卫执行逻辑:更严格的类型和异步处理

Vue Router4 对路由守卫的参数类型做了更严格的定义,beforeEach 的参数是 (to: RouteLocation, from: RouteLocation, next: NavigationGuardNext) => void | Promise<void>,如果在守卫里用异步操作(比如接口请求),要确保 next 的调用时机,避免导航卡住。

对“空路由”的处理更友好

Vue Router4 中,首次进入时 from 是一个“空路由对象”(包含 name: null 等属性),而不是 Vue2 里的 undefined,所以判断 from.name 是否为 null 更安全:

router.beforeEach((to, from) => {
  if (from.name !== null) { // 首次进入时 from.name 是 null
    //

版权声明

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

发表评论:

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

热门