Vue3 里怎么实现页面跳转?不同场景下有哪些方法?
在 Vue3 项目里,页面跳转是实现路由交互的核心操作之一,不管是做导航栏切换、按钮点击跳转,还是带参数跳转到详情页,不同场景得用不同方法才顺手,今天就把 Vue3 里常见的页面跳转方式、传参技巧,还有容易踩的坑,一次性唠明白~
声明式跳转:用 router-link 轻松实现
Vue 里专门搞了个 <router-link>
组件来做声明式导航,不用写 JS 代码,靠标签就能完成跳转。
先看最基础的用法,直接指定跳转路径:
<router-link to="/home">回到首页</router-link>
这就跟 HTML 里的 <a>
标签功能类似,但 <router-link>
更“智能”——它会自动处理单页应用的路由切换,不会像 <a>
那样整页刷新。
要是需要传参数,有两种常见写法:
- 传 query 参数(参数会显示在 URL 里,
/detail?id=1
):<router-link :to="{ path: '/detail', query: { id: 1, title: 'xxx' }}"> 跳转到详情页 </router-link>
- 传 params 参数(参数藏在路由配置里,URL 里不显示,得配合
name
使用):
先在路由配置里给页面配个name
:const routes = [ { path: '/detail/:id', name: 'Detail', component: Detail } ]
然后在
<router-link>
里用name
加params
:<router-link :to="{ name: 'Detail', params: { id: 1 }}"> 跳转到详情页 </router-link>
啥时候适合用 <router-link>
?像导航栏、侧边菜单这种“点一下就跳转”的静态交互,用声明式写法特别清爽,不用写事件处理函数~
编程式跳转:用 router.push / replace / go 灵活控制
要是跳转逻辑需要判断(比如点击按钮后先验证表单,再决定跳不跳),这时候就得用编程式导航,靠 JS 代码控制跳转。
Vue3 里要先通过 useRouter
拿到路由实例,再调用对应的方法,步骤如下:
-
引入并使用
useRouter
:import { useRouter } from 'vue-router' export default { setup() { const router = useRouter() // 接下来用 router 里的方法跳转 } }
-
常用的跳转方法:
- router.push():最常用的跳转,会往历史记录里新增一条,就像浏览器的“前进”按钮逻辑。
用法有三种:- 直接写路径字符串:
router.push('/home')
- 用对象配
path
:router.push({ path: '/detail', query: { id: 1 } })
- 用对象配
name
+params
:router.push({ name: 'Detail', params: { id: 1 } })
(记得路由配置里要有name
)
- 直接写路径字符串:
- router.replace():替换当前历史记录,不会新增,比如从 A 页跳 B 页,用 replace 的话,返回时会直接跳过 A 页到更前面的页面,适合“登录后替换登录页”这种场景。
用法和 push 差不多:router.replace('/home')
或者传对象。 - router.go(n):控制历史记录的前进后退,
n
是数字。router.go(1)
是前进一页,router.go(-1)
是后退一页,跟浏览器的history.go(n)
一个逻辑。
- router.push():最常用的跳转,会往历史记录里新增一条,就像浏览器的“前进”按钮逻辑。
举个实际例子,点击按钮验证用户是否登录,再跳转:
<button @click="handleJump">跳转到个人中心</button>
import { useRouter } from 'vue-router' import { useUserStore } from '@/stores/user' // 假设用 Pinia 存用户信息 export default { setup() { const router = useRouter() const userStore = useUserStore() const handleJump = () => { if (userStore.isLogin) { router.push('/profile') // 已登录跳个人中心 } else { router.push('/login') // 未登录跳登录页 } } return { handleJump } } }
带参数的页面跳转,query 和 params 咋选?
做项目时,跳转到详情页、订单页经常需要传参数,Vue3 里主要用 query
和 params
两种方式,得搞清楚它们的区别:
对比项 | query 参数 | params 参数 |
---|---|---|
URL 显示 | 会显示在 URL 里(如 ?id=1 ) |
不显示在 URL 里(靠路由配置隐藏) |
刷新后是否保留 | 刷新页面参数还在 | 刷新页面参数会丢失 |
路由配置要求 | 不需要配动态路由 | 需要配动态路由(如 /detail/:id ) |
query 传参 & 接收
传参(以编程式为例):
router.push({ path: '/detail', query: { id: 1, type: 'goods' } })
URL 会变成 http://xxx/detail?id=1&type=goods
接收参数:用 useRoute
拿到当前路由信息,取 query
里的值:
import { useRoute } from 'vue-router' export default { setup() { const route = useRoute() const id = route.query.id // 注意:query 里的参数是字符串,需要自己转数字 const type = route.query.type console.log(id, type) // 1, goods } }
params 传参 & 接收
传参(必须配合 name
,不能用 path
!因为 path
会忽略 params
):
router.push({ name: 'Detail', params: { id: 1, title: '商品详情' } })
路由配置得提前写好动态参数:
const routes = [ { path: '/detail/:id', // 这里的 `:id` 对应 `params.id` name: 'Detail', component: Detail } ]
接收参数:同样用 useRoute
取 params
:
import { useRoute } from 'vue-router' export default { setup() { const route = useRoute() const id = route.params.id const title = route.params.title console.log(id, title) // 1, 商品详情 } }
注意:如果路由配置里没写对应的动态参数(比如上面路由只配了 :id
,但传了 title
),title
刷新后会丢失,URL 里也不会显示。params
适合传“刷新后不重要,或者需要隐藏的参数”,query
适合传“刷新后要保留,或者需要分享的参数”~
新窗口打开页面,咋搞?
单页应用里,默认的 router.push
是在当前页面内切换路由,如果要打开新标签页/新窗口,得换思路:
方法 1:用 router.resolve 生成 URL,再 window.open
先通过 router.resolve
把路由信息转成完整的 URL,再用 window.open
打开。
例子:点击按钮在新窗口打开详情页
<button @click="openNewWindow">新窗口打开详情</button>
import { useRouter } from 'vue-router' export default { setup() { const router = useRouter() const openNewWindow = () => { // 生成目标路由的完整信息 const target = router.resolve({ name: 'Detail', params: { id: 1 } }) // 新窗口打开(_blank 表示新标签页) window.open(target.href, '_blank') } return { openNewWindow } } }
方法 2:动态创建 a 标签,设置 target="_blank"
原理和上面一样,只是用 a 标签的方式(更符合 HTML 语义化):
<a :href="targetHref" target="_blank" rel="noopener noreferrer" >新窗口打开详情</a>
import { computed } from 'vue' import { useRouter } from 'vue-router' export default { setup() { const router = useRouter() const targetHref = computed(() => { const target = router.resolve({ name: 'Detail', params: { id: 1 } }) return target.href }) return { targetHref } } }
为啥不能直接用 router.push
开新窗口?因为 router.push
是操作当前页面的路由历史,单页应用里所有内容都在一个 HTML 里切换,新窗口需要加载全新的页面实例,所以必须用 window.open
或者 a 标签~
(小知识:rel="noopener noreferrer"
是为了安全——noopener
防止新窗口篡改当前页面,noreferrer
防止泄露当前页面的 referrer 信息。)
路由守卫里咋控制跳转?
有时候需要“拦截跳转”,比如用户没登录就访问个人中心,得强制跳登录页;或者页面离开前验证表单是否保存,这时候得用路由守卫。
Vue3 里路由守卫分全局守卫、组件内守卫,这里重点讲常用的 全局前置守卫 beforeEach 和 组件内守卫 onBeforeRouteUpdate / onBeforeRouteLeave。
全局前置守卫 beforeEach
作用:每次路由跳转前都会触发,能全局控制权限(比如登录拦截)。
用法:在路由配置文件(router/index.js
)里写:
import { createRouter, createWebHistory } from 'vue-router' import { useUserStore } from '@/stores/user' // 假设用 Pinia 存用户 const routes = [/* 你的路由配置 */] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置守卫 router.beforeEach((to, from, next) => { const userStore = useUserStore() // 判断目标页面是否需要登录(靠路由的 meta 元信息标记) if (to.meta.requiresAuth && !userStore.isLogin) { // 需要登录但没登录,跳登录页 next('/login') } else { // 放行 next() } }) export default router
这里的 to.meta.requiresAuth
是给路由加的自定义元信息——除了控制权限,还能存页面标题、菜单图标等,比如给路由配标题:
const routes = [ { path: '/profile', name: 'Profile', component: Profile, meta: { requiresAuth: true, title: '个人中心' } } ]
组件内守卫 onBeforeRouteUpdate / onBeforeRouteLeave
作用:在组件内部控制路由,比如页面跳转前提示“表单未保存,是否离开?”
用法:在组件的 setup
里引入并使用:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router' import { useFormStore } from '@/stores/form' // 假设用 Pinia 存表单数据 export default { setup() { const formStore = useFormStore() // 页面离开前触发(比如从当前页跳走时) onBeforeRouteLeave((to, from) => { if (formStore.isDirty) { // 表单有未保存内容 const confirm = window.confirm('表单还没保存,确定要离开吗?') if (confirm) { // 确定就放行(Vue3 组合式 API 里的组件内守卫,无需手动调用 next,逻辑判断即可) } else { // 取消就留在当前页(抛错阻止跳转) throw new Error('取消离开') } } }) // 路由参数变化时触发(比如从 /detail/1 跳 /detail/2,组件复用的情况) onBeforeRouteUpdate((to, from) => { // 可以在这里更新数据,比如根据新的 params.id 重新请求接口 console.log('路由参数变了,新的 id 是', to.params.id) }) return {} } }
注意:全局守卫(如 beforeEach
)里必须调用 next()
放行/跳转;但组件内守卫(onBeforeRouteXXX
)在 Vue3 组合式 API 中,更推荐用逻辑判断控制(比如上面用 throw Error
阻止跳转),不用手动调 next
啦~
跳转时常见问题 & 解决办法
实际开发中,跳转经常遇到“页面不刷新”“参数丢了”“重复跳转报错”这些坑,一个个解决:
问题 1:路由参数变了,但页面没刷新
场景:比如从 /detail/1
跳转到 /detail/2
,因为路由配置里 path: '/detail/:id'
,组件会被复用(性能优化),所以组件的生命周期不会重新执行,导致页面数据没更新。
解决方法:用 watch
监听路由变化,或者用组件内守卫 onBeforeRouteUpdate
。
-
用
watch
监听:import { useRoute, watch } from 'vue-router' export default { setup() { const route = useRoute() watch( () => route.params.id, // 监听 params.id 的变化 (newId) => { // 新的 id 变化了,重新请求数据 fetchData(newId) } ) } }
-
用
onBeforeRouteUpdate
:前面讲组件内守卫时举过例子,在这个守卫里处理数据更新~
问题 2:params 传参,刷新页面后参数丢了
原因:params
参数默认存在内存里,不在 URL 中,所以刷新后会丢失。
解决方法:
- 改成
query
传参(适合参数能暴露在 URL 的情况)。 - 或者把
params
参数对应的动态路由配置写全,并且用name
跳转(虽然刷新还丢,但至少跳转时能拿到)。 - 进阶玩法:把参数存在 Pinia / Vuex 里,刷新时从存储中取(但得配合持久化,Pinia 插件存 localStorage)。
问题 3:重复点击路由,控制台报错 “Uncaught (in promise) NavigationDuplicated”
原因:Vue Router 3.x 以上版本对重复导航做了限制,重复调用 router.push
同一个路由会报错。
解决方法:
-
简单粗暴:全局捕获异常(适合项目里很多地方都有重复跳转的情况):
// 在 router/index.js 里写 const originalPush = router.push router.push = function push(location) { return originalPush.call(this, location).catch(err => err) }
-
优雅点:跳转前判断是否已经是目标路由:
import { useRouter, useRoute } from 'vue-router' export default { setup() { const router = useRouter() const route = useRoute() const handleJump = () => { if (route.path !== '/target') { // 判断当前是否已经是目标路由 router.push('/target') } } return { handleJump } } }
Vue3 里的页面跳转得根据场景选方法:静态导航用 router-link
省心,逻辑复杂的用 router.push/replace
灵活,传参分 query/params
各有适用场景,新窗口打开得靠 router.resolve + window.open
,路由守卫能搞权限和拦截,把这些方法吃透,不管是做后台管理系统的菜单切换,还是电商项目的商品详情跳转,都能游刃有余~要是你还有其他跳转相关的疑问,评论区随时喊我~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。