在 Vue Router 中,实现页面跳转有两种方式:声明式导航和编程式导航。它们本质上是同一个路由系统提供的两套接口,只是使用场景和书写方式不同。
声明式导航:用组件写跳转
声明式导航使用 <router-link> 组件,在模板中直接声明跳转目标。它会自动渲染成一个 <a> 标签,并处理路径匹配、激活状态等细节。
<template>
<nav>
<!-- 最简形式 -->
<router-link to="/home">首页</router-link>
<!-- 动态绑定路径 -->
<router-link :to="`/user/${userId}`">用户中心</router-link>
<!-- 使用对象指定路由 -->
<router-link :to="{ name: 'UserProfile', params: { id: 123 } }">
用户资料
</router-link>
</nav>
</template>
声明式导航的适用场景:
- 导航菜单、面包屑、侧边栏等静态的、渲染时就确定的链接
- 需要浏览器右键新标签页打开的链接(
<router-link>渲染为真实<a>标签,支持target="_blank") - 希望通过模板清晰展示页面跳转关系,便于代码阅读
内置功能:
<router-link> 会自动给当前激活的链接添加 router-link-active 或 router-link-exact-active class,方便你定义高亮样式。
/* 当前激活的导航项高亮 */
.router-link-active {
color: #1890ff;
font-weight: bold;
}
编程式导航:用 JS 控制跳转
编程式导航通过调用 router 实例的方法(push、replace、go 等)来实现跳转,适合在逻辑代码中根据条件动态改变路由的场景。
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
// 登录成功后跳转到首页
async function handleLogin() {
await loginApi()
router.push('/home')
}
// 提交表单后跳转并传递查询参数
function submitForm() {
router.push({
path: '/result',
query: { status: 'success' }
})
}
// 替换当前历史记录,不产生新的浏览记录
function redirectToLogin() {
router.replace('/login')
}
// 前进或后退
function goBack() {
router.go(-1) // 等价于 history.back()
}
function goForward() {
router.go(1) // 等价于 history.forward()
}
</script>
编程式导航的适用场景:
- 操作后的跳转:登录、登出、表单提交、支付完成等需要先执行一段逻辑再跳转
- 权限拦截后的重定向:路由守卫检测到未登录,调用
next('/login')或router.push('/login') - 复杂的路径计算:需要根据多个变量动态拼接路径,或根据后端返回数据决定跳转地址
- 需要精确控制历史记录:使用
replace替代push,避免用户按返回键退回到过时状态
两者的关系与选择
<router-link> 底层也是调用 router.push,所以声明式导航本质上是对编程式导航的模板封装。选择时只需看你要跳转的时机:
| 场景 | 推荐方式 |
|------|----------|
| 纯展示的导航链接,点击即跳转 | <router-link>(声明式) |
| 需要先执行异步操作再跳转 | router.push(编程式) |
| 需要根据业务逻辑决定跳转到不同页面 | router.push |
| 需要在 JS 工具函数或全局拦截器中跳转 | router.push |
| 需要禁止回退(如退出登录后) | router.replace |
一个常见的小坑:如果在一个已经处于 /home 的页面再次调用 router.push('/home'),Vue Router 会抛出一个 NavigationDuplicated 警告(旧版本会直接报错)。这通常发生在用户快速连点导航按钮时。修复方法很简单:
// 方式1:添加全局错误处理(推荐)
router.push('/home').catch(err => {
// 忽略重复导航错误
if (err.name !== 'NavigationDuplicated') throw err
})
// 方式2:直接检查当前路由
if (router.currentRoute.value.path !== '/home') {
router.push('/home')
}
总结一句话: 在模板里写跳转用 <router-link>,在逻辑里写跳转用 router.push,两者可以混合使用,背后都是同一个路由实例在工作。