Vue Router 怎么获取上一个路由?previous route 实用技巧全解析
做 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) // 上一个路由对象 } }
⚠️ 注意:首次进入应用时,from
是 undefined
(因为没有“上一个路由”),所以业务逻辑里要判断 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
应用第一次加载时,没有“上一个路由”,from
是 undefined
,如果直接访问 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 route
是 undefined
,因为它是全新的会话。
怎么兼容?可以结合浏览器存储(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/1
,to
是 /user/2
——这属于“同一路由不同参数”,from
依然是有效的上一个路由。
但如果业务逻辑里把“同路由不同参数”视为“没跳转”,就需要额外判断 from.path
和 to.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.$route
和 this.$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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。