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

一、在导航守卫里用from参数直接拿上一个路由

terry 4小时前 阅读数 7 #Vue

在Vue项目开发里,经常会遇到需要知道“用户从哪个页面跳过来”的场景,比如做返回按钮、埋点统计、权限校验这些功能时,获取上一个路由(previous route)就成了关键需求,那Vue Router到底怎么实现这个功能?不同场景下有哪些技巧?下面一步步拆解答案。

Vue Router的导航守卫机制,本身就提供了获取“来源路由”的入口——from参数,不管是全局守卫、路由独享守卫,还是组件内守卫,都能通过这个参数拿到上一个路由的信息。

先看全局前置守卫router.beforeEach),这是最常用的全局拦截逻辑,举个例子:

// 全局守卫,每次路由跳转前触发
router.beforeEach((to, from, next) => {
  console.log('从哪个页面来?', from.path) // 打印上一个路由的路径
  console.log('要到哪个页面去?', to.path)
  next() // 必须调用next放行,否则路由跳转会卡住
})

这里的from就是上一个路由的路由对象,里面包含path(路径)、name(路由命名)、meta(自定义元信息)、params(动态参数)等关键属性。

再看组件内的导航守卫,比如beforeRouteEnter(组件渲染前触发):

export default {
  // 组件内守卫,此时组件实例还没创建,this访问不到
  beforeRouteEnter(to, from, next) {
    console.log('从', from.path, '进入当前组件')
    next()
  },
  // 组件内守卫,路由参数变化时触发(/user/:id 从id=1跳到id=2)
  beforeRouteUpdate(to, from, next) {
    console.log('参数变化,上一个路由是', from.path)
    next()
  },
  // 组件内守卫,离开当前组件时触发
  beforeRouteLeave(to, from, next) {
    console.log('要离开当前组件,跳转到', to.path, ';之前是从', from.path, '来的')
    next()
  }
}

注意哦,beforeRouteEnter执行时组件实例还没生成,所以不能用this;但beforeRouteUpdatebeforeRouteLeave里可以用this访问组件数据~

还有路由独享守卫(在路由配置里写beforeEnter):

const routes = [
  {
    path: '/about',
    component: About,
    // 只有进入/about时才触发这个守卫
    beforeEnter: (to, from, next) => {
      console.log('从', from.path, '进入/about页面')
      next()
    }
  }
]

这种方式适合给某个路由单独加“来源判断”逻辑,不用全局拦截所有路由~

借助路由历史记录手动维护上一个路由

导航守卫能覆盖“正常路由跳转”的情况,但如果用户是直接在地址栏输入URL刷新页面,这时候导航守卫里的from可能是undefined(比如首次进入应用时,没有上一个路由),这时候就得自己维护路由历史记录啦。

思路很简单:每次路由跳转后,把当前路由存起来,作为下一次的“上一个路由”,可以用全局变量、Vuex、Pinia或者浏览器存储(sessionStorage、localStorage)来实现。

举个用全局变量的例子(适合简单项目):

// 在router.js里定义一个变量存上一个路由
let previousRoute = null
router.afterEach((to, from) => {
  // 路由跳转完成后,把当前to存为下一次的from
  previousRoute = from
})
<p>// 然后在需要的地方(比如组件里)导入这个变量
import router from './router'
console.log('上一个路由是', previousRoute)

但全局变量有个问题:页面刷新后会丢失数据,所以更可靠的是用浏览器存储,比如sessionStorage(会话级存储,关闭标签页就清空):

// 全局后置守卫,每次路由跳转后记录
router.afterEach((to, from) => {
  if (from) { // 防止首次进入时from为undefined
    sessionStorage.setItem('previousRoute', JSON.stringify(from))
  }
})
<p>// 在组件里读取
export default {
mounted() {
const prevRoute = JSON.parse(sessionStorage.getItem('previousRoute'))
console.log('上一个路由(可能是刷新前的)', prevRoute)
}
}

如果用状态管理工具(比如Pinia),可以更优雅地维护:

// store/routeStore.js
import { defineStore } from 'pinia'
export const useRouteStore = defineStore('route', {
  state: () => ({
    previousRoute: null
  }),
  actions: {
    setPreviousRoute(route) {
      this.previousRoute = route
    }
  }
})
<p>// router.js里引入并使用
import { useRouteStore } from './store/routeStore'
router.afterEach((to, from) => {
const routeStore = useRouteStore()
routeStore.setPreviousRoute(from)
})</p>
<p>// 组件里使用
import { useRouteStore } from './store/routeStore'
export default {
setup() {
const routeStore = useRouteStore()
console.log('上一个路由', routeStore.previousRoute)
}
}

这种方式不管是路由跳转、刷新还是地址栏输入,只要之前有过路由记录,就能拿到相对可靠的“上一个路由”~

特殊场景:直接输入URL或刷新页面怎么处理?

前面提到,用户直接在地址栏输入URL或者刷新页面时,Vue Router的导航守卫里的from可能是undefined(因为这时候没有“路由跳转”行为,是全新的页面加载),这时候要分两种情况处理:

首次进入应用:这时候确实没有上一个路由,逻辑上可以设为null或者默认路由(比如首页)。

刷新页面:这时候想拿到“刷新前的上一个路由”,就得靠持久化存储(比如sessionStorage、localStorage),前面讲的“手动维护路由历史”方案就能解决这个问题——刷新前把路由信息存在存储里,刷新后再读出来。

举个完整的刷新处理例子:

// router.js
router.beforeEach((to, from, next) => {
  // 刷新时,from可能是undefined,所以先从sessionStorage取
  const storedPrev = sessionStorage.getItem('previousRoute')
  if (storedPrev) {
    // 这里可以把from替换成存储的路由(仅作逻辑处理,实际导航守卫的from是只读的)
    console.log('刷新前的上一个路由', JSON.parse(storedPrev))
  }
  next()
})
<p>router.afterEach((to, from) => {
if (from) {
sessionStorage.setItem('previousRoute', JSON.stringify(from))
}
})

这样一来,就算用户刷新页面,也能通过sessionStorage恢复“上一个路由”的信息啦~

Vue Router 3.x和4.x版本的差异

Vue生态里,Vue 2搭配Vue Router 3.x,Vue 3搭配Vue Router 4.x,两个版本在“获取上一个路由”的细节上有不少区别,得注意兼容性~

导航守卫的语法变化

  • Vue Router 3.x的全局守卫是router.beforeEach((to, from, next) => { next() }),必须调用next()来控制导航;
  • Vue Router 4.x的全局守卫是router.beforeEach((to, from) => { return true }),通过返回值(或Promise)来控制导航,不再需要next

举个Vue Router 4.x的全局守卫例子:

// Vue Router 4.x
router.beforeEach((to, from) => {
  console.log('从', from.path, '到', to.path)
  return true // 放行,也可以返回false阻止,或返回路由对象重定向
})

组件内守卫的写法差异

Vue 3的组件用setup语法糖时,组件内守卫要换成组合式API的写法,比如在<script setup>里,用onBeforeRouteUpdateonBeforeRouteLeave

// Vue 3 + Vue Router 4.x 的组件内守卫
<script setup>
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
<p>onBeforeRouteUpdate((to, from) => {
console.log('参数变化,上一个路由是', from.path)
})</p>
<p>onBeforeRouteLeave((to, from) => {
console.log('离开当前组件,之前从', from.path, '来的')
})
</script>

而Vue Router 3.x在Vue 2的选项式API组件里,还是用beforeRouteEnter这类选项式守卫~

路由实例的获取方式

Vue Router 3.x中,在组件里用this.$router获取路由实例;Vue Router 4.x中,在setup语法糖里要通过useRouter组合式API获取:

// Vue 3 + Vue Router 4.x
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
console.log(router.currentRoute.value) // 当前路由信息
</script>

这些版本差异得留意,否则容易出现“代码明明对,就是不生效”的情况~

实际项目中的应用场景

知道了怎么获取上一个路由,得落地到真实需求里才有价值,下面举几个常见场景,看看怎么结合技术方案实现~

场景1:自定义返回按钮

很多项目的头部有“返回”按钮,需要跳转到上一个页面,如果是普通跳转,用this.$router.back()就行,但有时候要特殊处理(比如从特定页面来的要跳转到其他地方),这时候结合上一个路由的判断:

// Vue 2 + Vue Router 3.x 示例
<template>
  <button @click="goBack">返回</button>
</template>
<script>
export default {
  data() {
    return {
      prevRoute: null
    }
  },
  beforeRouteEnter(to, from, next) {
    next(vm => {
      vm.prevRoute = from // 把from赋值给组件实例的prevRoute
    })
  },
  methods: {
    goBack() {
      if (this.prevRoute && this.prevRoute.path === '/special-page') {
        // 从特殊页面来的,跳转到指定页面
        this.$router.push('/target-page')
      } else {
        // 否则返回上一页
        this.$router.back()
      }
    }
  }
}
</script>

场景2:面包屑导航

面包屑需要显示“当前页面的上级页面”,这时候上一个路由(或父级路由)就是关键,比如路由配置是嵌套路由:

const routes = [
  {
    path: '/user',
    name: 'User',
    component: UserLayout,
    children: [
      { path: 'profile', name: 'UserProfile', component: UserProfile }
    ]
  }
]

在UserProfile组件里,上一个路由可能是User页面,所以面包屑可以显示“用户中心 > 个人资料”,这时候用导航守卫的from参数,或者路由的父级关系(this.$route.parent)来实现~

场景3:页面访问统计

统计用户从哪个页面进入当前页面,用于分析流量来源,可以在全局后置守卫里记录:

// 结合埋点工具,比如百度统计、Google Analytics
router.afterEach((to, from) => {
  if (from) {
    // 发送埋点数据:来源页面from.path,目标页面to.path
    window._hmt.push(['_trackPageview', to.path, from.path])
  }
})

场景4:权限控制

有些页面要求必须从特定页面跳转过来才能访问(比如支付成功页,必须从支付页跳转),这时候在路由守卫里判断from:

router.beforeEach((to, from, next) => {
  if (to.name === 'PaymentSuccess' && from.name !== 'Payment') {
    // 不是从支付页来的,重定向到首页
    next({ name: 'Home' })
  } else {
    next()
  }
})

这些场景覆盖了大部分业务需求,核心都是“先拿到上一个路由,再做逻辑判断”~

常见问题与解决方案

实际开发中,“获取上一个路由”容易碰到一些坑,这里总结几个高频问题和应对方法~

问题1:from参数是undefined

原因:用户直接输入URL、刷新页面、首次进入应用时,没有“上一个路由”,所以from为undefined。

解决:结合“手动维护路由历史”的方案,用sessionStorage或状态管理工具存之前的路由;同时在代码里做兜底判断(比如if (!from) return)。

问题2:嵌套路由下from的准确性

场景:比如父路由是/user,子路由是/user/profile,从/user跳到/user/profile时,from是/user,这没问题;但如果从其他页面(比如/home)跳到/user/profile,from是/home,这也符合预期,但要注意,嵌套路由的导航守卫里,from是“直接的来源路由”,不是父路由哦~

解决:如果需要父路由信息,可以用to.matched数组(包含所有嵌套的父路由),或者手动维护父路由关系。

问题3:动态路由参数变化时from的内容

场景:路由是/user/:id,从/user/1跳到/user/2,这时候触发的是beforeRouteUpdate守卫,from是/user/1的路由对象,包含params: { id: 1 }

注意:这时候from的path是/user/1,name如果是动态路由的命名(比如'User'),params里能拿到旧的id,所以可以正常区分参数变化前后的路由~

问题4:Vue Router 4.x中useRouter获取

版权声明

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

发表评论:

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

热门