如果你做过 Todo List 之类的单页组件练习,下一步自然会想:怎么做出多个页面?
点击文章列表 → 跳转到详情页 → 再返回列表。这听起来像是最基本的需求,但在 SPA(单页应用)里,没有"页面"这个概念——整个应用只有一个 HTML 文件,全靠 JavaScript 来控制显示什么、隐藏什么。
Vue Router 就是来解决这个问题的。
路由三件套
Vue Router 的核心就是三条指令/函数,记住它们,整个路由系统就清楚了:
routes<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', component: Home },
{ path: '/post/:id', name: 'detail', component: Detail },
{ path: '/:pathMatch(.*)*', name: 'notfound', component: NotFound },
]
const router = createRouter({
history: createWebHistory(),
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(路由器实例)上没有 params,params 在 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 = [
{ id: 1, title: "活着", author: "余华", time: "2025-05-17", context: "..." },
{ id: 2, title: "刻意练习", 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 是 false,find 找不到。记得 Number() 转一下。
4. 404 路由必须放最后 — /:pathMatch(.*)* 会匹配所有路径,如果放在 / 前面,首页就永远匹配不到了。
最终效果
打开页面看到文章列表,点击任意卡片进入详情页,点"返回"回到列表。路由栏的 URL 也会跟着变化——/ → /post/3 → /。这就是 SPA 路由的精髓:URL 变了,页面没刷新,内容变了。
博客首页 → 点击"活着" → 文章详情页
/posts/ → 点击卡片 → /post/1
┌──────────┐ ┌──────────────┐
│ 📚 文章列表 │ │ ← 返回 │
│ │ │ │
│ 活着 │ ──────────→ │ 活着 │
│ 刻意练习 │ │ 余华 · 2025 │
│ 杀死一只 │ │ │
│ 知更鸟 │ │ 福贵这辈子… │
└──────────┘ └──────────────┘总结
Vue Router 的核心只有三样东西:
1. routes配置 — 定义路径和组件的映射关系2. <router-view>— 组件渲染出口3. <router-link>+useRouter()/useRoute()— 导航和参数获取
配上 <script setup> 的组合式 API,写起来比 Vue 2 时代清爽太多了。不需要在 this.$router 和 this.$route 之间纠结,useRouter() 和 useRoute() 的命名已经告诉你该用哪个。
下次遇到需要"多页面"的场景——不管是博客、后台管理、还是商城——这三件套就是你的起点。