大家好,我是小杨,一个有着6年前端开发经验的老司机。今天想和大家聊聊我在Vue项目中最爱干的事——封装组件。就像乐高积木一样,好的组件能让开发效率翻倍,项目结构更清晰。下面我就分享几个我实际项目中封装的组件,以及背后的设计思路。
1. 智能表单生成器:告别重复劳动
在后台管理系统开发中,表单占据了70%的工作量。每次都要写一堆el-form-item,于是我决定封装一个智能表单生成器。
// FormGenerator.vue
export default {
props: {
formConfig: {
type: Array,
required: true
},
formData: {
type: Object,
required: true
}
},
render(h) {
return h('el-form', [
this.formConfig.map(item => {
return h('el-form-item', {
props: {
label: item.label,
prop: item.prop
}
}, [
this.renderFormItem(h, item)
])
})
])
},
methods: {
renderFormItem(h, item) {
const { type, options, placeholder } = item
const props = {
value: this.formData[item.prop],
placeholder: placeholder || `请输入${item.label}`,
on: {
input: val => {
我.$set(this.formData, item.prop, val)
}
}
}
switch(type) {
case 'input':
return h('el-input', { props })
case 'select':
return h('el-select', { props }, [
options.map(opt => h('el-option', {
props: {
label: opt.label,
value: opt.value
}
}))
])
// 其他类型...
}
}
}
}
使用起来超级简单:
<template>
<form-generator
:form-config="formConfig"
:form-data="formData"
/>
</template>
<script>
export default {
data() {
return {
formData: { name: '', gender: '' },
formConfig: [
{
label: '姓名',
prop: 'name',
type: 'input'
},
{
label: '性别',
prop: 'gender',
type: 'select',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' }
]
}
]
}
}
}
</script>
设计亮点:
- 配置化驱动,减少模板代码
- 支持动态扩展表单类型
- 自动双向绑定,无需手动写v-model
- 内置常用表单校验规则
2. 动态表格组件:数据展示的瑞士军刀
后台管理系统总少不了各种表格展示,我封装了一个支持动态列、排序、分页的超级表格组件。
// SmartTable.vue
export default {
props: {
columns: {
type: Array,
required: true
},
data: {
type: Array,
required: true
},
pagination: {
type: Object,
default: () => ({
currentPage: 1,
pageSize: 10,
total: 0
})
}
},
methods: {
handleSortChange({ prop, order }) {
this.$emit('sort-change', { prop, order })
},
handlePageChange(page) {
this.$emit('page-change', page)
},
renderHeader(h, column) {
return column.label
},
renderCell(h, scope, column) {
const { prop, formatter } = column
const cellValue = scope.row[prop]
if (formatter) {
return formatter(cellValue, scope.row)
}
if (column.slot) {
return this.$scopedSlots[column.slot](scope)
}
return cellValue
}
},
render(h) {
return h('div', [
h('el-table', {
props: {
data: this.data,
border: true
},
on: {
'sort-change': this.handleSortChange
}
}, [
this.columns.map(column => {
return h('el-table-column', {
props: {
label: column.label,
prop: column.prop,
sortable: column.sortable,
width: column.width
},
scopedSlots: {
header: scope => this.renderHeader(h, column),
default: scope => this.renderCell(h, scope, column)
}
})
})
]),
h('el-pagination', {
props: {
currentPage: this.pagination.currentPage,
pageSize: this.pagination.pageSize,
total: this.pagination.total,
layout: 'total, sizes, prev, pager, next, jumper'
},
on: {
'current-change': this.handlePageChange,
'size-change': this.handlePageChange
}
})
])
}
}
使用示例:
<template>
<smart-table
:columns="columns"
:data="tableData"
:pagination="pagination"
@page-change="handlePageChange"
@sort-change="handleSortChange"
>
<template #status="{ row }">
<el-tag :type="row.status | statusType">
{{ row.status | statusText }}
</el-tag>
</template>
</smart-table>
</template>
为什么这么设计?
- 列配置与数据解耦,灵活性高
- 支持自定义渲染和插槽
- 内置分页和排序逻辑
- 减少重复的表格样式代码
3. 可拖拽的树形菜单:让交互更友好
在做一个CMS系统时,需要实现菜单的拖拽排序功能,于是有了这个组件。
// DraggableTree.vue
import { Tree, MessageBox } from 'element-ui'
export default {
extends: Tree,
methods: {
handleDrop(draggingNode, dropNode, dropType) {
try {
const draggingData = draggingNode.data
const dropData = dropNode.data
// 验证是否允许放置
if (dropType === 'inner' && !dropData.isFolder) {
MessageBox.warning('只能拖拽到文件夹内')
return
}
// 触发父组件事件
this.$emit('node-drop', {
draggingNode,
dropNode,
dropType
})
// 调用原始Tree的drop方法
this.$options.extends.methods.handleDrop.call(
this,
draggingNode,
dropNode,
dropType
)
} catch (error) {
console.error('拖拽失败:', error)
MessageBox.error('拖拽操作失败')
}
}
}
}
增强功能:
- 继承原生el-tree所有功能
- 添加拖拽验证逻辑
- 友好的错误提示
- 保持原始API不变,易于迁移
4. 图片上传预览组件:一站式解决方案
图片上传是高频需求,我封装了一个带预览、裁剪、压缩的增强版上传组件。
// AdvancedUpload.vue
export default {
data() {
return {
dialogVisible: false,
cropImg: '',
fileList: []
}
},
methods: {
beforeUpload(file) {
const isImage = file.type.includes('image/')
const isLt5M = file.size / 1024 / 1024 < 5
if (!isImage) {
this.$message.error('只能上传图片!')
return false
}
if (!isLt5M) {
this.$message.error('图片大小不能超过5MB!')
return false
}
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = event => {
this.cropImg = event.target.result
this.dialogVisible = true
resolve(false) // 暂停默认上传
}
})
},
handleCrop() {
// 获取裁剪后的图片
this.$refs.cropper.getCroppedCanvas().toBlob(blob => {
const croppedFile = new File([blob], 'cropped_' + Date.now() + '.jpg', {
type: 'image/jpeg'
})
// 手动添加到文件列表
this.fileList.push({
uid: Date.now(),
name: croppedFile.name,
size: croppedFile.size,
raw: croppedFile
})
this.dialogVisible = false
}, 'image/jpeg', 0.8) // 80%质量压缩
}
}
}
功能特色:
- 内置图片类型和大小校验
- 集成图片裁剪功能
- 自动压缩图片质量
- 保持与el-upload一致的API
组件封装的心得体会
经过这些年封装组件的经验,我总结了几个原则:
- 单一职责原则:一个组件只做一件事,做好一件事
- 开放封闭原则:对扩展开放,对修改封闭
- 约定优于配置:提供合理的默认值,减少必须的配置项
- 保持一致性:API设计遵循项目或框架的约定
- 文档和示例:好的组件必须配有好的文档和示例
记住,不要为了封装而封装。当发现某段代码被复制粘贴超过3次,或者一个组件超过500行代码时,就该考虑拆分了。
最后
组件封装是前端工程化的重要部分,好的组件就像乐高积木,能让团队开发效率倍增。希望我的这些经验对你有帮助。如果你有更好的组件封装思路,欢迎在评论区交流!
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!