现在位置: 首页 > Vue3 教程 > 正文

路由跳转

本章你将学会用 Vue Router 实现前后端分离的页面跳转,包括动态路由参数。


为什么要用路由?

传统的多页面网站,点击链接会向服务器请求新的 HTML 文件,页面整个刷新,有白屏闪烁。

Vue Router 实现的是 前端路由:URL 变化时不向服务器发请求,而是由 JS 动态切换显示的组件。

这样做的好处:无刷新切换页面,体验像原生 APP 一样流畅


Vue Router 核心概念

概念作用对应代码
路由表定义 URL 路径与组件的映射关系routes 数组
路由出口匹配到的组件显示的位置<RouterView />
导航链接点击跳转到指定路由<RouterLink to="...">
动态路由URL 中包含变量(如文章 ID)/post/:id

安装与配置

如果你在创建项目时已选择 Router,可以跳过安装步骤。

如果没选,终端执行:

$ npm install vue-router@4

创建路由配置文件

实例

// 文件路径:src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

// 路由表:每条记录映射一个路径到一个组件
const routes = [
  {
    path: '/',                // URL 路径
    name: 'home',             // 路由名称(可选,编程式导航时用)
    component: HomeView       // 对应的页面组件
  },
  {
    // 动态路由::id 是占位符,匹配任意值
    // /post/1、/post/2、/post/hello 都会匹配到这条
    path: '/post/:id',
    name: 'post',
    // 懒加载:访问时才加载,打包时会拆分出独立的 JS 文件
    component: () => import('../views/PostView.vue')
  }
]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(),  // 使用 HTML5 History 模式(URL 看起来像普通路径)
  routes                         // 注册路由表
})

export default router

两种路由模式的区别:createWebHistory 产生干净的 URL(/post/1),需要服务器配置支持。如果部署环境不确定,可以用 createWebHashHistory,URL 中带 # 号(/#/post/1),无需服务器配置。

在 main.js 中注册路由

实例

<!-- 文件路径:src/App.vue -->
<script setup>
import NavBar from './components/NavBar.vue'
</script>

<template>
  <NavBar />
  <main>
    <!-- RouterView 是魔法位置:当前路由匹配到的组件会在这里渲染 -->
    <RouterView />
  </main>
</template>

RouterView 是路由系统的核心出口。

当 URL 为 / 时,它会渲染 HomeView;当 URL 为 /post/1 时,它会渲染 PostView。


RouterLink — 声明式导航

RouterLink 是 Vue Router 提供的导航组件,替代了传统的 <a> 标签。

它不会触发页面刷新,只会通过 JS 切换显示的组件。

实例

<template>
  <!-- 普通写法 -->
  <RouterLink to="/">首页</RouterLink>

  <!-- 命名路由写法(更推荐:路径变了也只改一处) -->
  <RouterLink :to="{ name: 'post', params: { id: article.id } }">
    阅读文章 {{ article.id }}
  </RouterLink>

  <!-- 传统 a 标签会导致页面刷新,不要用 -->
  <!-- <a href="/post/1">阅读全文</a> -->
</template>

useRoute — 读取路由参数

在详情页 PostView 中,需要知道 URL 中的文章 ID 是多少,才能找到对应的文章。

useRoute 就是用来读取当前路由信息的。

实例

<!-- 文件路径:src/views/PostView.vue -->
<script setup>
import { useRoute } from 'vue-router'

// useRoute 返回当前路由对象
const route = useRoute()

// route.params 包含 URL 中的参数
// 例如 URL 为 /post/3,则 route.params.id === '3'
console.log('文章 ID:', route.params.id)
</script>

<template>
  <div>
    <h1>文章详情页</h1>
    <!-- 注意:params 中的值都是字符串 -->
    <p>当前文章 ID:{{ route.params.id }}</p>
  </div>
</template>

route.params.id 的值始终是字符串。即使 URL 是 /post/3,取到的也是 '3' 而非数字 3。需要数字时,用 Number(route.params.id)parseInt 转换。


动手:完整的文章详情页

把 BlogCard 改成可点击跳转,详情页根据 ID 显示完整文章内容。

第一步:改造 BlogCard 支持点击跳转

实例

<!-- 文件路径:src/components/BlogCard.vue -->
<script setup>
defineProps({
  id: Number,       // 新增:文章 ID,用于跳转
  title: String,
  summary: String,
  date: String,
  category: String
})
</script>

<template>
  <!-- 整个卡片被 RouterLink 包裹,点击任意位置都能跳转 -->
  <RouterLink :to="{ name: 'post', params: { id } }" class="card-link">
    <div class="card">
      <span class="tag">{{ category }}</span>
      <h3>{{ title }}</h3>
      <p>{{ summary }}</p>
      <span class="date">{{ date }}</span>
    </div>
  </RouterLink>
</template>

<style scoped>
.card-link { text-decoration: none; color: inherit; display: block; }
.card {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.tag {
  display: inline-block;
  padding: 2px 10px;
  background: #e8f5e9;
  color: #42b883;
  border-radius: 12px;
  font-size: 12px;
}
.card h3 { margin: 10px 0 8px; font-size: 18px; }
.card p { color: #666; font-size: 14px; line-height: 1.6; }
.date { font-size: 12px; color: #999; }
</style>

第二步:创建 PostView 详情页

详情页从 URL 中读取 ID,然后从文章数据中找到对应的文章显示。

实例

<!-- 文件路径:src/views/PostView.vue -->
<script setup>
import { ref, computed } from 'vue'
import { useRoute, RouterLink } from 'vue-router'

const route = useRoute()
const id = Number(route.params.id)

// 所有文章数据(后续章节会抽离到公共模块)
const articles = ref([
  {
    id: 1,
    title: 'Vue3 入门完全指南',
    summary: '从零开始学习 Vue3 组合式 API',
    category: 'Vue',
    date: '2024-05-10',
    content: `
      <h2>为什么学 Vue3?</h2>
      <p>Vue3 是目前最流行的前端框架之一,它易学、灵活、性能出色。</p>
      <h2>组合式 API 的优势</h2>
      <p>相比 Vue2 的选项式 API,组合式 API 让逻辑复用变得简单,代码组织更加灵活。</p>
      <h2>ref 与 reactive</h2>
      <p>ref 适合基本类型数据,reactive 适合对象和数组。但实际开发中推荐统一使用 ref。</p>
    `
  },
  {
    id: 2,
    title: 'JS 异步编程详解',
    summary: '搞懂 Promise 和 async/await',
    category: 'JavaScript',
    date: '2024-05-08',
    content: `
      <h2>什么是异步?</h2>
      <p>JS 是单线程的,异步操作可以让主线程不阻塞,同时处理网络请求、定时器等任务。</p>
      <h2>Promise 的核心概念</h2>
      <p>Promise 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。</p>
    `
  }
])

// 根据 ID 查找文章
const article = computed(() => {
  return articles.value.find(a => a.id === id)
})
</script>

<template>
  <div class="post-view">
    <!-- 文章不存在 -->
    <div v-if="!article" class="not-found">
      <h2>文章不存在</h2>
      <p>找不到 ID 为 {{ id }} 的文章</p>
      <RouterLink to="/">返回首页</RouterLink>
    </div>

    <!-- 文章存在,展示完整内容 -->
    <article v-else>
      <span class="category-tag">{{ article.category }}</span>
      <h1>{{ article.title }}</h1>
      <time>{{ article.date }}</time>
      <!-- v-html 渲染 HTML 字符串 -->
      <div class="content" v-html="article.content"></div>
      <RouterLink to="/" class="back-link">← 返回首页</RouterLink>
    </article>
  </div>
</template>

<style scoped>
.post-view {
  max-width: 720px;
  margin: 40px auto;
  padding: 0 20px;
}

.category-tag {
  display: inline-block;
  padding: 4px 12px;
  background: #e8f5e9;
  color: #42b883;
  border-radius: 12px;
  font-size: 13px;
  margin-bottom: 12px;
}

h1 {
  font-size: 32px;
  margin-bottom: 12px;
  line-height: 1.4;
}

time {
  display: block;
  color: #999;
  font-size: 14px;
  margin-bottom: 30px;
}

.content {
  line-height: 1.8;
  font-size: 16px;
  color: #333;
}

.content :deep(h2) {
  margin: 24px 0 12px;
  font-size: 22px;
}

.content :deep(p) {
  margin-bottom: 12px;
}

.back-link {
  display: inline-block;
  margin-top: 40px;
  color: #42b883;
  text-decoration: none;
}

.not-found {
  text-align: center;
  padding: 60px 0;
}
</style>

v-html 用于渲染 HTML 字符串,但要注意:不要用它渲染用户输入的内容,否则有 XSS 安全风险。这里的文章内容是开发者自己写的,所以是安全的。


本章小结

本章你学会了 Vue Router 的核心使用:路由表配置(path 映射到 component)、RouterLink 导航、RouterView 出口、动态路由 /post/:id、以及 useRoute 读取路由参数。

现在博客有了真正的多页面体验——首页浏览列表,点击进入详情页阅读完整内容。