Vue 生态三大核心库概览
Q: 掌握了 Vue3 基础之后,要构建一个完整的中大型应用,还需要哪些生态库?
A: Vue 生态中最核心的三个库分别是:前端路由库(Vue Router)、状态管理库(Pinia)、前端组件库(如 Element Plus)。这三个库覆盖了页面导航、全局状态共享、UI 快速搭建三大核心诉求,是任何一个 Vue3 项目的标配。
什么是前端路由
Q: 路由不是后端才有的概念吗?前端为什么也要有路由?
A: 这个问题的答案藏在 Web 开发模式的演变中。
多页应用时代的后端路由
在早期的多页应用(MPA)时代,路由完全属于后端。后端根据不同的请求路径返回不同的完整 HTML 页面。
这个时期的两个显著特点:
- 整个项目的前后端代码是杂糅在一起的。
- 每次切换页面,都需要重新请求服务器,加载一个全新的 HTML。
单页应用时代的前端路由
随着 Ajax 技术的普及,应用进入了单页应用(SPA)时代。整个应用只有一个 HTML 页面,以前「整个页面切换」变成了「页面上某个模块的切换」。
这个模块在 Vue 中就是页面级组件(通常放在 views 目录下)。页面级组件与 URL 路径之间需要建立映射关系——这就是前端路由的核心职责。
值得注意的是,有了前端路由后,后端路由并未消失,而是从「路由和页面的映射」变成了「路由和数据接口的映射」。
Vue Router 快速入门
Q: Vue Router 具体怎么用?有哪些核心概念?
A: Vue Router 是 Vue 官方推出的前端路由库,与 Vue3 深度集成。首先安装:
npm install vue-router@4
第一步:创建页面级组件
在 src/views/ 目录下创建页面组件,例如 Home.vue 和 About.vue。
第二步:配置路由
在 src/router/index.js 中定义路由映射:
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const router = createRouter({
history: createWebHistory(), // 路由模式:history 或 hash
routes: [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About },
],
});
export default router;
第三步:挂载路由实例
在 main.js 入口文件中挂载:
import router from '@/router';
const app = createApp(App);
app.use(router).mount('#app');
第四步:在组件中使用
Vue Router 提供了两个核心组件:
| 组件 | 作用 |
|---|---|
<router-link> |
导航链接,to 属性指定目标路由路径 |
<router-view> |
路由匹配的组件渲染位置(占位出口) |
<template>
<div id="app">
<h1>欢迎来到 Vue Router 快速入门示例</h1>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view />
</div>
</template>
理解要点:
<router-link>本质是<a>标签的封装,但避免了页面的全量刷新。<router-view>是动态组件渲染的出口,URL 变化时自动切换显示对应组件。
KeepAlive 内置组件
Q: 在页面间切换时,上一个页面的状态(如表单填写、滚动位置)会丢失,如何保留?
A: Vue 提供了 KeepAlive 内置组件来解决这个问题——它会缓存被包裹组件的状态,组件切换后不销毁,状态得以保留。
基础用法
配合 Vue Router 使用时,通过 router-view 的作用域插槽获取当前匹配的组件,再用 KeepAlive 包裹:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
其中 component 并非真正的组件,而是模板语法中的特殊元素(和 slot、template 类似),用于动态渲染组件——具体渲染哪个组件由 is 属性决定。这些特殊元素在模板编译期间会被编译掉。
包含与排除
使用 include / exclude 属性来精细控制哪些组件需要缓存:
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
实际场景中配合路由使用:
<router-view v-slot="{ Component }">
<keep-alive include="Counter,Timer">
<component :is="Component" />
</keep-alive>
</router-view>
注意:使用逗号分隔字符串时,组件名之间不要加空格。
最大缓存数
通过 max 属性限制缓存实例的数量:
<router-view v-slot="{ Component }">
<keep-alive :max="3">
<component :is="Component" />
</keep-alive>
</router-view>
当缓存的实例数量即将超过指定最大值时,最久没有被访问的缓存实例将被销毁,为新的实例腾出空间。这实际上是 LRU(最近最少使用)缓存策略。
状态管理库 Pinia
Q: 小项目用 Props + Emit 传递数据就够了,为什么还需要引入状态管理库?
A: 当应用规模逐步变大,不同层级之间组件状态的传递会变得非常麻烦。兄弟组件、跨层级组件之间的数据共享,靠逐层传递 Props 和手动触发事件很快就会失控。
状态管理库的解决思路是:建立一个统一的数据仓库,任何一个组件将状态提交到仓库,另一个组件直接从仓库获取最新状态,组件之间不再直接通信。
Pinia 简介
Pinia(发音 /piːnjʌ/,西班牙语「菠萝」之意)是 Vue 官方推荐的新一代状态管理库,用于替代 Vuex。由 Vue.js 核心团队成员开发,最初是作为 Vuex 5 的实验性探索,后来因实现了 Vuex 5 提案的大部分内容,直接取代了 Vuex。
Pinia vs Vuex
| 特性 | Vuex | Pinia |
|---|---|---|
| mutations | 必须通过 mutation 修改 state | 不再有 mutations,只有 state、getters、actions |
| 异步操作 | 只能在 action 中,mutation 必须同步 | actions 中支持同步和异步 |
| TypeScript | 类型推断较差 | 可靠的类型推断支持 |
| 模块嵌套 | 支持模块嵌套 | 不再有模块嵌套,只有 Store,Store 间可互调 |
| 体积 | 较大 | 更轻量,压缩后仅 2KB 左右 |
| 插件扩展 | 有限 | 支持插件扩展,可方便实现本地存储等功能 |
Pinia 快速入门
安装:
npm install pinia
在 main.js 中挂载:
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia).mount('#app');
创建数据仓库(src/stores/useCounterStore.js):
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() { this.count++; },
decrement() { this.count--; },
},
});
在组件中使用:
<template>
<div class="counter">
<h1>计数器:{{ counterStore.count }}</h1>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
</div>
</template>
<script setup>
import { useCounterStore } from './stores/useCounterStore.js';
const counterStore = useCounterStore();
</script>
核心要点:
defineStore接收仓库名称和配置对象两个参数。- 命名惯例使用
useXxxStore的形式。 - 组件中直接通过 store 实例访问 state 和调用 actions,无需 commit/dispatch。
Element Plus 组件库实战
Q: 在实际项目中,每个 UI 组件都从零手写效率太低。有没有现成的 Vue3 组件库可用?
A: Vue3 生态中有丰富的组件库可选。以下是几个主流选择:
| 组件库 | 特点 | 适用场景 |
|---|---|---|
| Element Plus | Element UI 的 Vue3 版本,企业级 | 后台管理系统 |
| Vuetify | 材料设计规范,组件丰富 | 快速开发各种应用 |
| Ant Design Vue | Ant Design 的 Vue 实现 | 企业级后台应用 |
| Naive UI | TypeScript 编写,轻量灵活,尤雨溪推荐 | 追求极简和类型安全 |
| Vant | 有赞出品的移动端组件库 | 移动端 H5 应用 |
安装与挂载
以 Element Plus 为例:
npm install element-plus --save
全量引入——简单直接,适合学习阶段:
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
const app = createApp(App);
app.use(ElementPlus).mount('#app');
注册后即可在任意组件中使用:
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
图标需要额外安装:
npm install @element-plus/icons-vue
全局注册图标:
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
按需引入(推荐生产使用)
全量引入会导致打包体积过大。推荐使用两个插件实现自动按需引入:
npm install -D unplugin-vue-components unplugin-auto-import
在 vite.config.js 中配置:
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
});
这两个插件的作用:
- unplugin-vue-components:自动扫描项目中使用的 Vue 组件,打包时自动按需引入,无需手动 import。
- unplugin-auto-import:自动引入 Vue 相关的工具函数(如 ref、computed),同样无需手动 import。
配置后 main.js 变得非常干净:
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');
Element Plus 表单实战
以下是使用 Element Plus 构建用户信息表单的完整示例:
<template>
<div class="form-container">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<h2 class="form-title">用户信息表单</h2>
<el-form-item label="姓名:" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="邮箱:" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-form-item label="出生日期:" prop="birthday">
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="性别:" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio value="male">男</el-radio>
<el-radio value="female">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="兴趣爱好:" prop="hobbies">
<el-checkbox-group v-model="form.hobbies">
<el-checkbox value="reading">阅读</el-checkbox>
<el-checkbox value="music">音乐</el-checkbox>
<el-checkbox value="sports">运动</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="接收通知:" prop="notifications">
<el-switch v-model="form.notifications" />
</el-form-item>
<el-form-item label="用户评级:" prop="rating">
<el-rate v-model="form.rating" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue';
const form = ref({
name: '', email: '', birthday: '',
gender: 'male', hobbies: [],
notifications: false, rating: 0,
});
const formRef = ref(null);
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
email: [
{ required: true, message: '请输入您的邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
],
birthday: [{ required: true, message: '请选择您的出生日期', trigger: 'change' }],
gender: [{ required: true, message: '请选择您的性别', trigger: 'change' }],
hobbies: [{ required: true, message: '至少选择一个兴趣爱好', trigger: 'change' }],
rating: [{ required: true, message: '请评价用户等级', trigger: 'change' }],
};
function submitForm() {
formRef.value.validate((valid) => {
if (valid) {
console.log('表单验证通过');
} else {
console.log('表单验证不通过');
}
});
}
</script>
国际化配置
Element Plus 默认使用英语文本。切换中文:
全量引入时:
import zhCn from 'element-plus/es/locale/lang/zh-cn';
app.use(ElementPlus, { locale: zhCn }).mount('#app');
按需引入时,使用 ElConfigProvider 组件:
<template>
<ElConfigProvider :locale="locale">
</ElConfigProvider>
</template>
<script setup>
import { ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
const locale = ref(zhCn);
</script>
总结
本文覆盖了 Vue3 生态的三大核心支柱:
| 生态库 | 解决的问题 | 核心概念 |
|---|---|---|
| Vue Router | 页面导航与 URL 映射 | router-link、router-view、history/hash 模式 |
| Pinia | 跨组件全局状态共享 | Store、state、getters、actions(无 mutations) |
| Element Plus | 快速搭建 UI 界面 | 组件库、全量/按需引入、国际化 |
最佳实践建议:
- 路由组织:将页面级组件统一放入
views/目录,路由配置放在router/下,结构清晰。 - 按需使用 KeepAlive:只对需要保留状态的页面(如表单页、列表页)开启缓存,不要对全部页面缓存。
- Pinia Store 拆分:按业务领域拆分多个 Store(如 userStore、cartStore),避免单一 Store 过于臃肿。
- 生产环境按需引入:项目上线时,使用
unplugin-vue-components+unplugin-auto-import实现按需引入,大幅减小打包体积。