五一假期,我开发了一个Form表单库,你觉得这样优雅吗?
表单开发是咱们前端的一个重要工作,基本上每天都在进行,然而我觉得目前的表单开发非常的不优雅,一直期望能够找出一个更加优雅的表单开发范式,在这个五一假期的最后1天,我开发了一个简单的库,希望能够给大家提供一些启发。
当前表单开发存在问题
首先看一下我们现在是如何进行开发表单的,假设我们要实现一个非常简单的表单,表单中只有2项内容,名称和标签,按照现在的习惯,我们大致实现如下:
<template>
<el-form
:model="formData"
size="mini"
label-width="100px"
>
<el-form-item
prop="name"
label="名称"
>
<el-input
v-model="formData.name"
placeholder="请填写名称"
minlength="2"
maxlength="30"
clearable
/>
</el-form-item>
<el-form-item
prop="tag"
label="标签"
>
<el-select
v-model="formData.tag"
class="w-100"
multiple
filterable
allow-create
default-first-option
placeholder="请选择或输入标签"
@visible-change="$event && getTags()"
>
<el-option
v-for="(item, index) in tagOptions"
:key="index"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
data(){
return {
formData: {
name: '',
tag: []
},
rules: {
name: [{required: true}],
tag: [{required: true}]
},
tagOptions: []
}
},
methods: {
getTags(){
//请求接口获取tags
this.tagOptions = [];
}
}
}
</script>
繁琐的DOM
仅仅只是2个表单项,Dom就有40来行代码,而随着表单项的增多,代码行数也会越来越多,每次看到这样繁琐的Dom都感觉头大,根本无法一眼看清整个表单的结构,特别是下拉选择类组件,还要通过v-for渲染option,总感觉这样的Dom结构非常的啰嗦。
表单配置分散
在上述示例中,标签tag的的实现分散在多个地方,Dom实现位于template中,校验规则rules位于data中,获取标签的方法getTags位于methods中,如果你想搞清楚tag的完整逻辑,势必要来回翻看查找相关代码,不符合高内聚的编程原则,期望每个表单项的相关内容集中到一起。
配置表单
通过配置驱动表单生成的方式,可以很好地解决上述问题,针对上述示例,我们可以通过配置表单改造为如下形式:
<template>
<config-form
:model="formData"
:fields="fields"
/>
</template>
<script>
export default {
name: 'App',
data() {
return {
formData: {
name: '',
tag: []
},
fields: {
name: {
label: '姓名',
component: 'input',
componentProps: {
placeholder: "请填写姓名",
minlength: "2",
maxlength: "30",
clearable: true
},
rules: [{required: true}]
},
tag: {
label: '标签',
component: 'select',
componentProps: {
class: "w-100"
multiple: true,
filterable: true,
allow-create: true,
placeholder: "请选择或输入标签",
options: () =>{
return request('/api/tags');
}
},
rules: [{required: true}]
}
}
};
}
};
</script>
可以看到,Dom部分大大减少,取而代之的是更加结构化的js配置;而且每个表单的信息几乎都集中在配置中,如果要修改一个表单项,就无需来回跳转查看逻辑了,可维护性明显增强。
编写表单的过程变成了写配置,只需要一股脑地编写fields对象的内容即可,不再需要一会去写Dom,一会去写逻辑,开发体验变的更好。
做配置表单,不得不面对3个问题
- 某个特殊表单项如何实现
- 表单项之间交互如何实现
- 如何实现自定义布局
第一个问题很好解决,只要借助插槽,就可以解决特殊表单项的定制。
<template>
<config-form
:model="formData"
:fields="fields"
>
<template slot="name-label">
<span style="color:red;">您的大名</span>
</template>
<template slot="name">
<input v-model="formData.name" /> <i class="el-icon-question"></i>
</template>
</config-form>
</template>
表单项交互主要体现在某些表单项的显示/隐藏、启用/禁用依赖其他表单项,针对每个表单项的配置,我增加了disabled和hidden属性配置,可以配置为函数。
const fields = {
name: {
label: '姓名',
component: 'input'
},
age: {
label: '年龄',
component: 'number',
disabled: (formData) => !formData.name
}
}
自定义布局是比较棘手的,如果全靠配置实现,会导致配置很繁琐,也无法实现一些特殊的样式,所以考虑通过占位符的形式,在Dom中配置每个表单项的位置和样式,但是表单项是渲染成input还是select,仍然由配置决定。
<template>
<config-form
:model="formData"
:fields="fields"
>
<el-row>
<el-col :span="24">
<!--这里只需要占位,具体渲染成input还是别的,由配置决定-->
<config-form-item prop="name"/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<config-form-item prop="age"/>
</el-col>
<el-col :span="12">
<config-form-item prop="sex"/>
</el-col>
</el-row>
</config-form>
</template>
<script>
export default {
name: 'App',
data() {
return {
formData: {
name: ''
},
fields: {
name: {
label: '姓名',
component: 'input'
},
age: {
label: '年龄',
component: 'input'
},
sex: {
label: '性别',
component: 'radio',
componentProps: {
options: [
{label: '男', value: 0},
{label: '女', value: 1},
]
}
}
}
};
}
};
</script>
在布局中,通过config-form-item对每个表单项进行占位,但是不编写具体实现代码,这样能让布局相关的Dom更加简洁可读,Dom的作用就是设定表单的样式,具体表单如何渲染,仍然由配置控制,通过这种方式,即保留了通过Dom编写布局的优势,又保留了通过数据驱动渲染的优势。
库的开发难点
配置表单主要还是在一些常见UI库上(如ElementUI)进行二次封装,但又不是简单的封装,比如对于Select、Radio、CheckBox等组件,我们期望它们能够通过配置的options来驱动渲染,options可以是数组或函数,而在ElementUI中,是不支持这种方式的,所以要在原有组件基础上进行封装。
const fields = {
sex: {
label: '性别',
component: 'radio',
componentProps: {
//支持配置一个数组,然后驱动radio的渲染
options: [
{label: '男', value: 0},
{label: '女', value: 1},
]
}
}
}
但是配置表单这个库不应该强制依赖具体某个UI组件库,不能把库和ElementUI绑死,所以考虑通过插件的形式进行组件库扩展,针对某种组件库封装一个插件,然后传递给ConfigForm库。
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import ConfigForm from "@config-form/v3";
//将ElementUI 和 ElementPlus 封装成一个插件
import ConfigFormPluginElement from '@config-form/plugin-element';
const app = createApp(App)
app.use(ElementPlus)
app.use(ConfigForm, {
//以插件形式传递给配置表单
presets: [ConfigFormPluginElement],
})
app.mount('#app')
如果要支持其他不同类型的组件库,只需要参照@config-form/plugin-element实现一份即可,目前仅实现了ElementUI和ElementPlus插件
源码地址: github源码
因为只花了一天时间进行实现,肯定还有很多不足,如果大家觉得这种方式确实对自己有用,后续我会逐渐优化,喜欢的同学麻烦帮点个小赞~~
你觉得这种方式开发表单更优雅吗?或者有什么好的想法欢迎一起讨论。
转载自:https://juejin.cn/post/7365003815509377065