人人都会AI编程

12.4 编程式导航与声明式导航

更新时间:2026-07-11

在 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-activerouter-link-exact-active class,方便你定义高亮样式。

/* 当前激活的导航项高亮 */
.router-link-active {
  color: #1890ff;
  font-weight: bold;
}

编程式导航:用 JS 控制跳转

编程式导航通过调用 router 实例的方法(pushreplacego 等)来实现跳转,适合在逻辑代码中根据条件动态改变路由的场景。

<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,两者可以混合使用,背后都是同一个路由实例在工作。