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



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