需求与分析
日常开发中,我们前端程序员可能需要开发大量类似的表单页面,就好像下面这些
或者这样的
作为初级前端,此时只能反复的通过Ctrl+C、V,俗称CV大法,然后改吧改吧,来完成需求。
但是这样仅仅能应付我们交付的压力,对于后期的维护、新页面的开发仍旧需要大量的时间去完成重复的工作。同时,最难堪的莫过于code review的时候,这种大量CV的代码只能成为团队集体诟病的典型例子。😂
如何解决
因此,在日常开发解决业务需求的同时,考虑如何提高工作的效率是程序员很重要的能力,我们需要设计一套能帮助咱们日常快速产出表单的工具,这样能大大提高工作效率。换言之,原本2天的工作量,仅需要半天完成,那剩下的1.5天不就可以摸[xué]鱼[xí]了吗😍
着手设计
开始设计吧!👍首先,我们先得分析自身的需求:
- 要能生成不同类型的表单元素
- 通过简单的配置可以定位或者栅格布局元素
- 能支持表单提交后的回调函数获取数据
- 更易维护核心代码编写方式【因为任何工具都不可能一次设计完美,所以未来可能需要修改】
设计流程图
为了保障我们不做重复事,不返工,最好先滤清思路,这里我用工具制作一张流程图。
😎怎么样,有图思路是不是清晰很多?建议小伙伴也都能养成这样的好习惯,需要这方面工具的可以联系我。
开始coding
数据结构
我们先将配置的数据结构定义成如下:
export const form = Object.freeze({
title: '注册用户',
items: [
[
{ label:'用户名',type: 'input', name: 'username', col: 2,value:123 },
],
[
{ label:'性别',type: 'select', name: 'gender', x:10,y:100, options: [
{ label: '男', value: 1 },
{ label: '女', value: 2 }
] },
]
]
});
其中items代表多行的表单元素,其中每个数组都代表一行。col代表当前元素需要跨越的栅格列数,name则为submit之后e元素的key。另外我们对每个表单的配置单独提取出来,作为一个页面的config.js,这样做是将结构、表单、业务逻辑进一步分离哦。另外Object.freeze是可以冻结对象,对于不需要改变的对象使用,可以提升性能哦
传递组件及组件接收的处理
<my-form @submit="onSubmit" :conf="form" />
我们在需要的页面将配置引入,并声明成data中的form属性,现在将其传递进去。接下来处理接收的万能表单组件啦
export default {
props: {
conf: {
type: Object,
default: () => ({}),
},
},
data() {
return {
form: {},
};
},
}
说明:
- conf对象为外部接收配置
- form对象为内部处理数据对象
难点处理
由于外部定义的name属性需要与表单元素绑定并实现响应式,所以这些属性,我们需要给内部form对象动态添加的属性,执行一下$set方法。
created() {
this.conf.items.forEach((row) => {
row.forEach((obj) => {
this.$set(this.form, obj.name, obj.value);
});
});
},
看懂了吗?说白了就是动态给的name作为key给form对象使用
渲染结构
我们先来看看大致结构:
<div class="form-container">
<h1>{this.conf.title}</h1>
<el-form ref="form" props={{ model: this.form }} label-width="80px">
{this.renderRows()} {/* 动态渲染行列,内部渲染元素*/}
<div style={DIRECTIONS[this.direction]}> {/* 动态按钮的方位*/}
<el-form-item>
<el-button type="primary"
onClick={(e) => this.$emit('submit',this.form)}>
立即创建 {/* ⬆触发事件传递数据(暂未加验证)*/}
</el-button>
<el-button>取消</el-button>
</el-form-item>
</div>
</el-form>
</div>
渲染元素
我们再来看看使用jsx渲染的行和列,如何处理细节:
renderRows() {
return this.conf.items.map((row) => {
return <el-row>{this.renderColumn(row)}</el-row>;
});
},
renderColumn(row) {
return row.map((element) => {
let styles = !element.col
? [
{ position: "absolute" },
{ left: element.x + "1px" },
{ top: element.y + "px" },
]
: null;
return (
<div class="row-container" style={styles}>
<el-form-item label={element.label}>
<el-col span={element.col}>{this.renderItem(element)}</el-col>
</el-form-item>
</div>
);
});
},
这里可以看到,我们用是否跨列来判断,不传递则是绝对定位,去除其x、y,来给元素定义定位的样式,之前传递的label就是在这里使用啦。
渲染表单元素
renderItem(element) {
switch (element.type) {
case "input":
return (
<el-input
onInput={(e) => (this.form[element.name] = e)}
value={this.form[element.name]}
></el-input>
);
case "select":
return (
<el-select
onChange={(e) => (this.form[element.name] = e)}
value={this.form[element.name]}
placeholder="请选择活动区域"
>
{element.options.map((e) => {
return <el-option label={e.label} value={e.value}></el-option>;
})}
</el-select>
);
}
},
这里的难点就是大家要对于【对象.[属性名变量]】的动态取值方法有一定的认识,另外如果你对jsx不太熟悉的话,可能也会有困扰。如果你用纯Vue的方式能先写出来,那也是ok的。
无所谓啦,jsx可以认为是一个更利于维护的编码方式,重要理解本课程思想。
最终效果
初始效果:
输入值
回车查看控制台
总结
本例可以帮助开发者,对于表单页开发,提供更为快速的配置生成方式,但目前只提供了基本的功能,一些其他细节上的功能还需要完善。也欢迎同学们与我交流、探讨功能的完善与技术的优化。