上篇回顾:2.2-02 对比了 Vue 可变数据与 React 不可变数据两种哲学。本篇讲 UI 组件库——后端转前端搭管理后台的「最后一公里」。你不需要从零写表单、表格、弹窗,用 Element Plus 或 Ant Design 能快速搭出专业界面。但「什么时候该封装业务组件、什么时候不该」是个重要的判断题。
一、开篇:从手写 CSS 到用组件库的转变
后端工程师第一次写管理后台,通常手写 HTML + CSS:
<table><tr><th>名称</th><th>操作</th></tr><tr><td>Java</td><td><button>编辑</button></td></tr></table>能用,但丑。加 CSS 美化要写半天,响应式、交互、校验全要手写。
用 Element Plus:
<el-table :data="tableData" stripe> <el-table-column prop="name" label="名称" /> <el-table-column label="操作"> <template #default="{ row }"> <el-button @click="edit(row)">编辑</el-button> </template> </el-table-column> </el-table>自带样式、分页、排序、加载态。组件库的价值不是「好看」,而是「把常见的交互模式封装成可复用的积木」。
二、两大主流组件库
| 维度 | Element Plus | Ant Design |
|---|---|---|
| 框架 | Vue3 | React(也有 Vue 版) |
| 风格 | 简洁、中后台 | 专业、设计规范完善 |
| 生态 | 国内中后台主流 | 国际化、大厂出品 |
| 文档 | 中文友好 | 英文为主 |
| 适合 | Vue 项目、国内业务 | React 项目、国际化业务 |
选型建议:Vue 项目用 Element Plus,React 项目用 Ant Design。
三、按需引入:tree-shaking 的价值
3.1 全量引入的问题
// 全量引入(不推荐)importElementPlusfrom'element-plus';import'element-plus/dist/index.css';app.use(ElementPlus);打包体积 1MB+,大部分组件你没用到。
3.2 按需引入
// Vite 配置自动按需引入importAutoImportfrom'unplugin-auto-import/vite';importComponentsfrom'unplugin-vue-components/vite';import{ElementPlusResolver}from'unplugin-vue-components/resolvers';exportdefault{plugins:[AutoImport({resolvers:[ElementPlusResolver()]}),Components({resolvers:[ElementPlusResolver()]}),]};模板里直接用<el-button>,构建时自动只打包用到的组件。体积从 1MB 降到几十 KB。
四、三大高频场景的标准解法
4.1 表单校验
<template> <el-form :model="form" :rules="rules" ref="formRef"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username" /> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="form.email" /> </el-form-item> <el-button @click="submit">提交</el-button> </el-form> </template> <script setup> import { ref, reactive } from 'vue'; const formRef = ref(); const form = reactive({ username: '', email: '' }); const rules = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '长度 3-20', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { type: 'email', message: '邮箱格式错误', trigger: 'blur' } ] }; const submit = async () => { await formRef.value.validate(); // 校验通过才继续 // 提交逻辑 }; </script>4.2 表格分页
<template> <el-table :data="tableData" v-loading="loading"> <el-table-column prop="name" label="名称" /> <el-table-column prop="createdAt" label="创建时间" /> </el-table> <el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :total="page.total" @change="loadData" /> </template>4.3 弹窗
<el-dialog v-model="visible" title="编辑"> <el-form :model="form"><!-- 表单内容 --></el-form> <template #footer> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="save">保存</el-button> </template> </el-dialog>五、主题定制与样式穿透
5.1 CSS 变量定制
:root{--el-color-primary:#409eff;--el-color-success:#67c23a;}5.2 样式穿透(:deep)
组件库的内部元素有 scoped 样式,外部改不了。用:deep:
<style scoped> :deep(.el-table .cell) { font-size: 14px; } </style>六、二次封装:什么时候该封、封什么
6.1 该封装的场景
- 重复组合:同一个表格+搜索+分页组合用了 5 次以上
- 业务规则:所有表单的邮箱字段都有相同的校验+提示
- 统一风格:全系统的按钮尺寸/颜色要一致
6.2 不该封装的场景
- 只用了 1~2 次:封装的代价比直接写还大
- 需求不稳定:业务还在变,封装后改起来更麻烦
- 过度抽象:把所有配置都做成 props,组件比原版还复杂
6.3 封装原则:开放封闭
<!-- 好的封装:固定业务部分,开放其余 --> <template> <el-table :data="data" :columns="columns"> <!-- 业务固定:分页、加载态内置 --> <!-- 业务可变:列配置、插槽透传 --> </el-table> </template>反面教材:把 el-table 的所有 props 都透传一遍,再加 20 个业务 props——这种封装比直接用 el-table 还复杂。
七、小结表
| 概念 | 价值 | 注意 |
|---|---|---|
| 按需引入 | 减小打包体积 | 用 unplugin 自动化 |
| 表单校验 | 减少手写校验逻辑 | rules 写在 setup 里 |
| 表格分页 | 标准化列表交互 | v-model 双向绑定页码 |
| 样式穿透 | 改组件库内部样式 | 用 :deep 不用 ::v-deep |
| 二次封装 | 复用业务模式 | 不过度封装 |