Vue 路由(Vue Router)
Vue 提供了官方的路由库 Vue Router,用于构建单页面应用(SPA,Single Page Application)
Vue Router 可以让应用在不刷新的情况下根据不同的 URL 显示不同的页面
Vue Router 核心功能:
- 在单页面应用中实现页面导航
- 根据 URL 动态加载对应的组件
- 支持浏览器前进、后退等功能
- 提供路由守卫用于权限控制和数据预加载
基本使用
- 在 Vue 项目中安装 Vue Router
1
| npm install vue-router@4 # 如果是 Vue2 请使用 vue-router@3
|
- 创建路由配置
创建一个 router/index.js 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import About from '../views/About.vue'
const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ]
const router = createRouter({ history: createWebHistory(), routes })
export default router
|
- 在主程序(
main.js )中使用 router
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createApp } from 'vue' import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) app.mount('#app')
|
- 在模板中使用
<router-link> 和 <router-view> 组件
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div> <h1>我的网站</h1> <nav> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> </nav> <router-view /> </div> </template>
|
基本概念
路由(Route)
路由 Route 定义了 URL 与组件之间的映射关系
语法:
1 2 3 4 5 6 7 8
| const routes = [ { path: '/', component: Home }, { path: '/about', component: About }
]
|
导航(Navigation)
“导航”指的是在不同路由之间进行跳转。在 Vue 中使用 <router-link> 组件或编程方式(如 router.push)进行页面跳转
声明式导航:
1 2 3 4
| <template> <router-link to="/home">Home</router-link> <router-view /> </template>
|
编程式导航:
编程式导航是在组件 script 中通过路由器实例 API 进行页面跳转的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <button @click="toHome">Home</button> <router-view /> </template>
<script setup> import { useRouter } from 'vue-router'
const router = useRouter()
const toHome = () => { router.push("/home") } </script>
|
路由器(Router)
路由器 Router 是路由管理的核心对象,用于配置路由并控制导航
创建路由器:
1 2 3 4 5 6
| import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({ history: createWebHistory(), routes, })
|
历史模式(History Mode)
Web 前端路由主要基于 History API 和 Location API 实现,一般包括两种模式:History 模式和 Hash 模式
Vue 中是用 createWebHistory() 和 createWebHashHistory() 来创建对应的历史模式,它会返回一个 RouterHistory 对象,Vue Router 内部会使用这个对象,用于跳转和监听浏览器地址栏 URL 的变化
Hash 模式:
Vue 中 Hash 模式是用 createWebHashHistory() 创建:
1 2 3 4 5 6 7 8 9
| import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({ history: createWebHashHistory(), routes: [ ], })
|
- 浏览器地址栏中 # 号后面的内容都是 hash 值,它不会包含在 HTTP 请求的路径中。其兼容性较好,无需后端特地去处理路径问题
History 模式:
Vue 中 History 模式是用 createWebHistory() 创建:
1 2 3 4 5 6 7 8 9
| import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({ history: createWebHistory(), routes: [ ] })
|
- History 模式中没有 # 号,比较符合正常的 URL,但在项目上线时,需要后端开发人员去分辨前端路由和后端路由,返回不同的资源,从而解决 Vue 单页应用在切换路由后,刷新页面,服务器返回 404 的问题
详细用法
命名路由
1 2 3 4 5 6
| { name: 'Message', path: '/message', component: Message }
|
1 2 3
| <router-link :to="{ name: 'Message' }">显示消息</router-link>
|
1 2 3
| import { useRouter } from 'vue-router' const router = useRouter() router.push({ name: 'Message' })
|
路由参数
参数类型:
| 类型 | 说明 | 示例 URL | 特点 |
|---|
| params 参数 | 路径参数(出现在路径中) | /user/123 | 需要先在路由配置中定义参数名(占位符) |
| query 参数 | 查询参数(?key=value 形式,多个 key=value 用 & 连接) | /user?id=123 | 不需在路由中预先定义参数名 |
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [ {
path: '/list1/:id', component: List }, {
path: '/list2', component: List } ]
|
在对应页面组件中可以
- 通过
$route.query.<key> 获取 query 参数 - 通过
$route.params.<name> 来获取 params 参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
<router-link to="/list1/123"> 显示列表1, 并传入 params 参数 </router-link> <router-link to="/list2?id=123"> 显示列表2, 并传入 query 参数 </router-link>
<router-link :to="{ path: '/list1', params: { id: 123 } }"> 显示列表1, 并传入 params 参数 </router-link> <router-link :to="{ path: '/list2', query: { id: 123 } }"> 显示列表2, 并传入 query 参数 </router-link>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| router.push({ path: '/list1', params: { id: 123 } })
router.push({ path: '/list2', query: { id: 123 } })
|
使用正则表达式动态匹配 params 参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| [ { path: '/user/:id?' }, { path: '/user/:id(\\d+)' }, { path: '/user/:id(\\d+)?' }, { path: '/user/:id([A-Za-z0-9]{6})' }, { path: '/:pathMatch(.*)*' } ]
|
子路由(嵌套路由)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| [ { path: '/user/:id?', component: User, children: [
{ path: 'detail', name: 'Detail', component: Detail } ] } ]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <h2>我是 User 组件</h2> <router-link to="/user/1/detail" active-class="active">前往用户1详情页面</router-link> <router-link to="/user/detail" active-class="active">前往用户详情页面</router-link> <router-link :to="{ name: 'Detail' }" active-class="active">前往用户详情页面</router-link>
<router-view></router-view> </template>
<style scoped> .active { color: red } </style>
|
重定向
1 2 3 4 5 6 7 8 9 10 11
| [ { path: '/', redirect: '/home' }, { path: '/home', component: Home } ]
|
路由守卫
缓存路由
Vue Router API
route 路由信息对象
route 路由信息对象,包含当前路由的:
- fullPath:完整 URL 路径,带参数
- name:名称
- params:params 参数
- path:基础路径
- query:查询字符串参数
可以通过 useRoute 来获取 route 对象
例:
1 2 3 4 5 6 7
| <script setup> import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route) </script>
|
router 路由器对象
router 路由器对象用于控制路由跳转(导航)
router.push()
用于跳转到新路由,会添加历史记录
例:
router.push('/home')router.push({ name: 'User', params: { id: 1 } })
router.replace()
跳转路由但不添加历史记录(替换当前历史记录)
例:
router.go()
前进/后退 n 个页面
例:
router.go(-2) 回退 2 个页面(正数为前进)
router.back()
回退到上一个路由
router.forward()
前进到下一个路由
router.addRoute()
添加路由
例:
router.addRoute({ path: '/about', component: About }) 添加根路由router.addRoute('Root', { path: '/user', component: User }) 向路由 name 为 Root 的路由添加子路由
router.removeRoute()
移除路由
例:
router.removeRoute('Other') 移除路由 name 为 Other 的路由
router.hasRoute()
判断是否存在某路由(true / false)
例:
router.hasRoute('Ahout') 检测路由 name 为 Ahout 的路由是否存在
router.getRoutes()
获取所有已经注册的路由记录
Vue 状态管理(Vue State Management)
状态(state) 就是程序中那些会影响 UI 显示的数据,一旦这些数据变化,页面显示也要跟着变化。状态管理的核心目的,就是 让这些数据的修改与使用更加可控、有序。
在 Vue 中,简单的项目可以用 props 和 events 进行组件通信,但当项目规模扩大后,就会遇到以下痛点:
- 跨层级通信麻烦(props drilling)
- 兄弟组件通信不方便
- 状态分散(不同组件各自维护,难以管理)
这时候就需要引入一种可以在多个组件之间共享状态的方案
Pinia
Pinia 是 Vue 官方推荐的新一代状态管理库,它的设计目标是 更轻量、更直观、更易用
Pinia 其名字来源 “菠萝”(西班牙语 pina)
Pinia 作为集中式状态管理库,可以帮助我们解决 Vue 中多组件间共享和管理全局状态混乱的问题,让状态管理更统一、更规范、更可维护
Pinia 的特点
- 更轻量:API 简单易学
- 更直观:和 Vue3 的 Composition API 风格一致
- 完整的 TypeScript 支持:类型推导更有好
- 插件系统:支持持久化存储(如 localStroage)、中间件扩展等
Pinia 基本使用
- 安装 pinia
- 在主程序(
main.js )中,创建并使用 Pinia
1 2 3 4 5 6 7 8 9 10 11
| import { createApp } from 'vue'; import { createPinia } from 'pinia'; import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia); app.mount('#app');
|
- 定义一个 Store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2 }, actions: { increment() { this.count++; } } });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { defineStore } from "pinia"; import { ref, computed } from "vue";
export const useCounterStore = defineStore("counter", () => { const count = ref(0); const doubleCount = computed(() => (state) => state.count * 2); const increment = () => count.value++;
return { count, doubleCount, increment, }; });
|
- 在组件中使用 Store
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { useCounterStore } from '@/stores/counter';
const counter = useCounterStore(); </script>
<template> <p>Count: {{ counter.count }}</p> <p>Double: {{ counter.doubleCount }}</p> <button @click="counter.increment">增加</button> </template>
|
Pinia 核心概念
Pinia 的核心就是 Store(仓库),它里面包含了 state、getters、actions
Store(仓库)
- Store 就是一个全局的 状态容器
- 每个 Store 都有唯一的
id - 可以有多个 Store,互相独立,也可以组合使用
定义一个 Store:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { defineStore } from 'pinia';
export const useCountStore = defineStore('count', { state: () => ({ count: 0 }) })
|
使用 Store:
1
| const countStore = useCountStore();
|
State(状态)
- 存放数据,类似 Vue 组件的
data - state 是响应式的,修改后会触发视图更新
例:
1 2 3 4 5 6 7 8 9 10
| import { defineStore } from 'pinia';
export const useCountStore = defineStore('count', { state: () => ({ count: 0, user: { name: 'Tom', age: 18 } }) })
|
1 2 3 4 5 6 7 8 9 10 11
| import { defineStore } from 'pinia'; import { ref, reactive } from "vue";
export const useCountStore = defineStore('count', () => { const count = ref(0); const user = reactive({ name: 'Tom', age: 18 })
return { count, user } });
|
在组件中访问 state:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <p>{{ countStore.count }}</p> <p>{{ countStore.user.name }}</p> <p>{{ countStore.user.age }}</p> </template>
<script setup> import { useCountStore } from '~/stores/counter.js';
const countStore = useCountStore();
countStore.count++ countStore.user.name = 'Steve' </script>
|
Getters(计算属性)
- 类似 Vue 的
computed,会缓存使用过的响应式数据,只有在这些响应式数据发生变化时才会重新计算并更新视图 - 用来派生出新数据,不会修改 state,只是基于 state 计算
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { defineStore } from 'pinia';
export const useCountStore = defineStore('count', { state: () => ({ count: 0, user: { name: 'Tom', age: 18 } }), getters: { doubleCount: (state) => state.count * 2, userInfo: (state) => `${state.user.name} (${state.user.age})` } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { defineStore } from 'pinia'; import { ref, reactive, computed } from "vue";
export const useCountStore = defineStore('count', () => { const count = ref(0); const user = reactive({ name: 'Tom', age: 18 })
const doubleCount = computed(() => count.value * 2) const userInfo = computed(() => `${user.name} (${user.age})`)
return { count, user, doubleCount, userInfo } })
|
在组件中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <p>{{ countStore.doubleCount }}</p> <p>{{ countStore.userInfo }}</p> </template>
<script setup> import { useCountStore } from '~/stores/counter.js';
const countStore = useCountStore();
countStore.count++ countStore.user.name = 'Steve' </script>
|
Actions(方法)
- 类似 Vue 组件的
methods - 用来修改状态、处理异步逻辑等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { defineStore } from 'pinia';
export const useCountStore = defineStore('count', { state: () => ({ count: 0, user: { name: 'Tom', age: 18 } }), actions: { increment() { this.count++; }, async fetchUser() { const res = await fetch('/api/user'); this.user = await res.json(); } } })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { defineStore } from 'pinia'; import { ref } from "vue";
export const useCountStore = defineStore('count', () => { const count = ref(0); const user = ref({ name: 'Tom', age: 18 });
const increment = () => count.value++; const fetchUser = async () => { const res = await fetch('/api/user'); user.value = await res.json(); };
return { count, increment, fetchUser } });
|
在组件中使用:
1 2 3 4 5 6 7 8 9 10
| <template> <button @click="countStore.increment">+1</button> <button @click="countStore.fetchUser">获取用户</button> </template>
<script setup> import { useCountStore } from '~/stores/counter.js';
const countStore = useCountStore(); </script>
|
Pinia 常用 API
$reset()
将 store 恢复到初始状态(定义时的初始值),但使用组合式 setup 函数定义的 Store 不能使用该方法(需要手动实现 $reset 方法)
$patch()
一次性批量修改多个 state 属性
避免多次触发响应式更新
1 2 3 4 5 6 7 8 9 10 11
| userStore.$patch({ name: 'Tom', age: 25 })
userStore.$patch((state) => { state.age++ state.name = 'Jerry' })
|
$subscribe()
监听整个 state 的变化
1 2 3 4 5 6 7 8
|
const stop = userStore.$subscribe((mutation, state) => { console.log('state changed:', mutation, state) })
|
$onAction()
监听 action 的调用
1 2 3 4 5 6 7 8 9 10 11 12
|
userStore.$onAction(({ name, store, args, after, onError }) => { console.log(`${name} action called with`, args) after(() => console.log(`${name} 完成`)) onError((error) => console.error(`${name} 出错`, error)) })
|
storeToRefs()
将 store 的响应式 state 和 getter 转为 refs,防止解构丢失响应式
1 2 3
| import { storeToRefs } from 'pinia' const user = useUserStore() const { name, age } = storeToRefs(user)
|
Pinia 插件
Pinia 插件(Plugin) 是用来「扩展 Pinia 功能」的一种机制。你可以在不修改原始 Store 代码的前提下,为所有 Store 添加新功能
1.定义一个插件
1 2 3 4 5 6 7 8 9 10 11
|
function myPlugin({ store, options, pinia }) { store.createdAt = new Date() }
|
2.使用插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
import { createApp } from 'vue' import { createPinia } from 'pinia'
import { myPlugin } from './plugins/myPlugin'
const app = createApp()
const pinia = createPinia()
pinia.use(myPlugin) app.use(pinia)
|
例(定义状态持久化插件):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
export function persistPlugin({ store }) { const key = `pinia-${store.$id}`
const fromStorage = localStorage.getItem(key) if (fromStorage) { store.$patch(JSON.parse(fromStorage)) }
store.$subscribe((mutation, state) => { localStorage.setItem(key, JSON.stringify(state)) }) }
|
使用状态持久化插件:
1 2 3 4 5 6 7 8 9 10
| import { createApp } from 'vue' import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/persist.js'
const app = createApp() const pinia = createPinia()
pinia.use(persistPlugin) app.use(pinia)
|