有么得前端小伙伴跟我有一样的烦恼,每次写表单就烦。这东西吧没有任何难度,就是纯体力活,项目中如果用了UI库,那基本每次就是照着官网实例复制粘贴。假设项目是用的vue + elementUI,那经常会出现这种代码
<template>
<div>
<el-input
v-model="data1"
placeholder="请输入内容"
@change="onchange" />
<el-input
v-model="data2"
type="textarea"
placeholder="请输入内容"
@change="onchange"/>
<el-switch
v-model="data3"
@change="onchange"/>
<el-slider
v-model="data4"
@change="onchange"/>
</div>
</template>
data(){
return{
data1:0,
data2:1,
data3:2,
data4:3,
}
}
methods:{
onchange(value){
........
}
}
我这还是写的故意简单一些,如果属性和事件更多那这个页面未来就很有可能发展成我们作为程序眼最怕的事情。
老大:“小陈,XXX离职了,你接手一下XXX文件,不难就是有点乱,也就一万多行吧”
我内心:“尼玛,明明几行代码加一个配置文件就搞定的事情,你可摇了我吧”
开始重构之路
(本文以vue+elementUI为例子,主要的是思想而不是代码)
- 分析代码,找出异同
相同点:elementUI中每所有表单元素最外层都被<form>
包裹,每一项都被<form-item>
包裹,每个具体的表单元素都是<el-XXX>
形式的,每个元素都可以通过value
或者v-model
来绑定数据。都具有一些公共的属性和事件,比如change。除了select,别的元素都可以用一行写完。
不同点:每个元素都有个性化的属性与方法,比如<el-input>
支持type属性,<el-slider>
支持max,min属性。 设计思路,代码实现
用一个数组对象来表示表单,每一项是一个object,里面包含了表单标签所有用到的属性,标签名tag、绑定字段model、标题label。
用一个对象来表示值,键值就是表单数组中声明过的那些。// 表单数组 let formList = [ { tag: 'input', model: 'name', label: '姓名' }, { tag: 'input-number', model: 'age', label: '年龄' }, { tag: 'switch', model: 'working', label: '是否在职' }, ] // 表单值 let formData = { name: '小陈', age: 26, working: false, }
数据的输入输出确定后就需要确定实现方式,可以写成一个vue组件,组件接受两个props:[formList,formData],之后对formList进行遍历,使用动态组件
<component>
来根据tag渲染出对应对标签,v-mode
传入的model字段。再将label传给<form-item>
,<template> <el-form> <el-form-item v-for="(item,index) in formList" :key="index" :label="item.label" > <component v-model="formData[item.model]" :is="`el-${item.tag}`" /> </el-form-item> </form> </template>
- 解决差异,大功告成
之前分析得出,最大的差异就是每个元素都用很多个性化的属性与事件,并且除了表单元素,<el-form>
`<el-form-item>`也有很多属性与事件,如果这些全都通过prop声明,那可太多了,并且还不好区分谁是谁的。所以需要用到了vue的$attrs和$listeners,
通过$attrs和$listeners批量绑定属性与事件,同时修改formList数据格式为
let formList = [
{
tag: 'input',
label: 'name',
model: 'name',
attrs: {
// 个性化属性
type: 'textarea'
},
listeners: {
// 个性化事件
change:(value)=>console.log(value,'change'),
blur:(value)=>console.log(value,'blur')
}
}
]
同时还要记得对select标签单独处理一下,最终代码为
```
<template>
<el-form v-bind="$attrs">
<template v-for="(item, index) in formList">
<el-form-item
:key="index"
v-bind="item.formItem"
v-if="item.tag === 'select'"
:label="item.label"
>
<el-select v-model="formData[item.model]" v-on="item.listeners" v-bind="item.attrs">
<el-option
:value="option.value"
:label="option.label"
:key="idx"
v-for="(option,idx) in item.options"
></el-option>
</el-select>
</el-form-item>
<el-form-item v-else :key="index" v-bind="item.formItem" :label="item.label">
<component
v-model="formData[item.model]"
v-on="item.listeners"
v-bind="item.attrs"
:is="`el-${item.tag}`"
></component>
</el-form-item>
</template>
</el-form>
</template>
<script>
export default {
props: {
formList: {
type: Array,
required: true,
},
formData: {
type: Object,
default: () => ({}),
},
},
};
</script>
<style>
</style>
```
*注意:本例子默认elementUI全局注册了哦*
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。