×

手把手搭一个带路由的博客:Vue Router 实战

独孤求败 独孤求败 发表于2026-06-11 09:16:19 浏览6 评论0

抢沙发发表评论

如果你做过 Todo List 之类的单页组件练习,下一步自然会想:怎么做出多个页面?

点击文章列表 → 跳转到详情页 → 再返回列表。这听起来像是最基本的需求,但在 SPA(单页应用)里,没有"页面"这个概念——整个应用只有一个 HTML 文件,全靠 JavaScript 来控制显示什么、隐藏什么。

Vue Router 就是来解决这个问题的。

路由三件套

Vue Router 的核心就是三条指令/函数,记住它们,整个路由系统就清楚了:

名字
作用
routes
 配置
告诉 Router:什么路径 → 渲染什么组件
<router-view>
当前路由的组件渲染在哪里
<router-link>
点击后跳转到指定路径

就像在一本书里翻页:routes 是目录(第5页 → 第五章),<router-view> 是书页(内容显示在这里),<router-link> 是页码(点击翻到第5页)。

第一步:配置路由

创建一个 src/router/index.js 文件,专门放路由配置:

import { createRouter, createWebHistory } from'vue-router'
importHomefrom"../components/Home.vue";
importDetailfrom"../components/Detail.vue";
importNotFoundfrom"../components/NotFound.vue";

const routes = [
    { path'/'name'home'componentHome },
    { path'/post/:id'name'detail'componentDetail },
    { path'/:pathMatch(.*)*'name'notfound'componentNotFound },
]

const router = createRouter({
    historycreateWebHistory(),
    routes
})

exportdefault router

重点是 routes 数组里的每一项:

  • • path — URL 路径,/post/:id 里的 :id 是动态参数
  • • name — 给路由起个名字,方便编程式跳转时用
  • • component — 路径匹配后渲染哪个组件

注意那个 /:pathMatch(.*)*,它是 catch-all 路由,匹配所有没定义过的路径,相当于 404 页面。这条必须放最后,Vue Router 按顺序匹配,放前面的先匹配到。

第二步:挂载到应用

路由配好了,要让 Vue 知道它的存在:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

关键就一行:.use(router)。如果你漏了这步,所有 <router-link> 和 <router-view> 都不会工作。

第三步:App.vue 里放路由出口

<!-- App.vue -->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <nav>
    <router-link to="/">📖 首页</router-link>
  </nav>

  <router-view />
</template>

<router-view> 就是 Vue Router 的渲染出口——当前路径匹配到哪个组件,就显示在哪里。

最常搞混的一对:useRouter vs useRoute

写路由组件时,有两个 hook 经常一起出现,但作用完全不同:

import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'

const router = useRouter()    // 遥控器——用来跳转
const route = useRoute()      // 节目单——用来读当前参数

// 用 Router:跳转
router.push('/post/3')
router.back()

// 用 Route:读 URL 里的参数
route.params.id     // /post/3 → "3"
route.query.page    // /list?page=2 → "2"

我第一次写的时候就踩了这个坑——用 router.params.id 去拿参数,结果报了 Cannot read properties of undefined。因为 router(路由器实例)上没有 paramsparams 在 route(当前路由信息)上。

记住口诀:跳转用 Router,读参数用 Route。

实战:写一个带路由的博客

理论讲完了,我们来做一个实际的博客应用。

项目结构

blog-vue/
├── src/
│   ├── main.js                ← 入口
│   ├── App.vue                ← 根组件(导航 + router-view)
│   ├── style.css              ← 全局样式
│   ├── router/
│   │   └── index.js           ← 路由配置
│   ├── data/
│   │   └── posts.js           ← 文章数据
│   └── components/
│       ├── Home.vue           ← 首页(文章列表)
│       ├── Detail.vue         ← 文章详情
│       └── NotFound.vue       ← 404 页面

准备数据

// src/data/posts.js
const dataList = [
    { id1title"活着"author"余华"time"2025-05-17"context"..." },
    { id2title"刻意练习"author"安德斯"time"2015-05-17"context"..." },
    // ... 更多文章
]
export default dataList

首页:文章列表

Home.vue 展示所有文章,点击卡片跳转到详情页。注意这里用的是编程式导航

<script setup>
import books from '../data/posts.js'
import { useRouter } from "vue-router";

const router = useRouter()
const getDetail = (id) => {
  router.push(`/post/${id}`)
}
</script>

<template>
  <div v-for="book in books" :key="book.id" @click="getDetail(book.id)">
    <h2>{{ book.title }}</h2>
    <p>{{ book.author }} · {{ book.time }}</p>
  </div>
</template>

也可以直接用 <router-link :to="/post/${book.id}"> 代替编程式导航,效果一样。用 router.push() 的好处是可以在跳转前做一些逻辑判断。

详情页:拿到路由参数

Detail.vue 要解决两个问题:拿到 URL 里的 id,找到对应文章。

<script setup>
import { useRoute, useRouter } from 'vue-router'
import books from '../data/posts.js'

const route = useRoute()         // ← 注意是 route 不是 router
const router = useRouter()       // ← 这个是用于返回按钮

const id = Number(route.params.id)
const book = books.find(p => p.id === id)
</script>

<template>
  <a href="javascript:void(0)" @click="router.back()">← 返回</a>

  <div v-if="book">
    <h1>{{ book.title }}</h1>
    <p>{{ book.author }} · {{ book.time }}</p>
    <p>{{ book.context }}</p>
  </div>
  <div v-else>
    <p>文章不存在</p>
  </div>
</template>

几个细节:

  • • route.params.id 是字符串,需要 Number() 转成数字再和 book.id 对比
  • • router.back() 模拟浏览器后退,比 router.push('/') 更自然——如果用户从首页跳转到详情页再返回,back() 会回到首页;如果用户从其他路径进来,也会回到上一个页面
  • • href="javascript:void(0)" 是为了让 <a> 标签保留链接样式,同时阻止默认跳转行为

防止文章不存在

如果用户手动输入 /post/999,或者 ID 不合法,find 会返回 undefined。用 v-if / v-else 做兜底:

<div v-if="book">
  <!-- 显示文章 -->
</div>
<div v-else>
  <p>文章不存在</p>
  <router-link to="/">返回首页</router-link>
</div>

从踩坑到跑通

这个博客练习虽然小,但我和它"切磋"了不少回合,整理几个值得记住的点:

1. 忘记 .use(router) — <router-view> 不会渲染任何内容,页面空白。检查 main.js

2. useRoute 用成了 useRouter — router.params 不存在,router.params.id 报 undefined。正确的用法是 route.params.id

3. route.params 是字符串 — /post/3 拿到的 id 是 "3"(字符串),而数据里的 id 是 3(数字)。"3" === 3 是 falsefind 找不到。记得 Number() 转一下。

4. 404 路由必须放最后 — /:pathMatch(.*)* 会匹配所有路径,如果放在 / 前面,首页就永远匹配不到了。

最终效果

打开页面看到文章列表,点击任意卡片进入详情页,点"返回"回到列表。路由栏的 URL 也会跟着变化——/ → /post/3 → /。这就是 SPA 路由的精髓:URL 变了,页面没刷新,内容变了。

博客首页          →  点击"活着"    →  文章详情页
/posts/           →  点击卡片      →  /post/1
┌──────────┐                     ┌──────────────┐
│ 📚 文章列表  │                     │ ← 返回       │
│            │                     │              │
│ 活着       │  ──────────→       │ 活着         │
│ 刻意练习    │                     │ 余华 · 2025  │
│ 杀死一只    │                     │              │
│ 知更鸟     │                     │ 福贵这辈子… │
└──────────┘                     └──────────────┘

总结

Vue Router 的核心只有三样东西:

  1. 1. routes 配置 — 定义路径和组件的映射关系
  2. 2. <router-view> — 组件渲染出口
  3. 3. <router-link> + useRouter()/useRoute() — 导航和参数获取

配上 <script setup> 的组合式 API,写起来比 Vue 2 时代清爽太多了。不需要在 this.$router 和 this.$route 之间纠结,useRouter() 和 useRoute() 的命名已经告诉你该用哪个。

下次遇到需要"多页面"的场景——不管是博客、后台管理、还是商城——这三件套就是你的起点。


群贤毕至

访客