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前端网发表,如需转载,请注明页面地址。
code前端网

