在开发表单时常见的有两种做法:一种是通过配置代码的方式,而另一种则是通过组件组合的方式。通过配置代码生成的表单,我们称之为动态表单(Dynamic Form),而通过组件组合生成的表单,我们称之为普通表单。
我发现很多人十分喜爱动态表单,在我经历过的十几个项目中,只要有表单需求,就一定会有人尝试通过配置代码去生成表单。但是,动态表单真的适合所有业务场景吗?动态表单有哪些坑?我们又应该如何在项目上应用动态表单?关于这些问题,我会在文章里逐一分析。
这个系列包含上下两篇文章:第一篇文章中我们会分析动态表单的适用场景,而第二篇文章中我们会介绍如何设计和实现复杂的动态表单:
动态表单(下)—— 如何实现复杂动态表单
配置 VS 组合
开头已经提到过构建表单常用的两种方式:配置和组合。接下来就让我们用一个具体的例子,看看如何通过这两种不同的方式来实现表单。
假设我们要完成一个用于收集个人信息的表单,包括性别、年龄和收入三个输入项。如下所示:
如果使用组合的方式,我们大致会写成这样:
export const ProductForm = () => {
return (
<form onSubmit={onSubmit}>
<div className={"form-body"}>
<Select
name={"gender"}
label={"Gender"}
options={genderList}
value={formValues.gender}
onChange={onFieldValueChange("gender")}
/>
<Input
name={"age"}
label={"Age"}
type={"number"}
value={formValues.age}
onChange={onFieldValueChange("age")}
/>
<Input
name={"income"}
label={"Income"}
type={"number"}
value={formValues.amount}
onChange={onFieldValueChange("income")}
/>
</div>
<div className={"form-footer"}>
<button type={"submit"}>Submit</button>
</div>
</form>
);
};
复制代码
如果使用配置的方式,我们大致会写成这样:
const formConfig = {
title: "个人信息",
description: "请填写个人信息",
fields: [
{
type: Select,
name: "gender",
label: "Gender",
value: "F",
},
{
type: Input,
name: "age",
label: "Age",
value: "",
},
{
type: Input,
name: "income",
label: "Income",
value: "",
}
],
}
export const ProductForm = () => {
return (
<form onSubmit={onSubmit}>
<div className={"form-body"}>
{
(formConfig.fields || []).map(({type, name, ...others}) => {
const Field = type;
return (
<div key={name}>
<Field
name={name}
onChange={onFieldValueChange(name)}
{...others}
/>
</div>
)
})
}
</div>
<div className={"form-footer"}>
<button type={"submit"}>Submit</button>
</div>
</form>
);
};
复制代码
通过上面的对比,也就不难理解很多人为什么如此热衷于动态表单了。对于动态表单来说,我们只需要定义一套统一的配置模板,之后新增表单或者输入项时,只需要修改配置文件即可,十分简单便捷。与组合的方式相比也少了许多重复的 HTML 模板代码。
生成动态表单的配置代码,一般来说会存储在后端,并通过 API 提供给前端使用。但本文中的动态表单也包括将配置代码存储在前端的方式。
那么,动态表单真的优于普通表单吗?请接着往下看。
动态表单的适用场景
动态表单这个词具有很强的迷惑性,因为「动态」这个词,乍一听会让很多人会觉得它的灵活性很强,能够适配很多业务场景。但其实动态表单最欠缺的就是灵活性,它只适合拥有相对固定业务需求和 UI 的表单。
比如你项目的需求几乎全是用于收集用户信息的问卷表单,并且这些表单的 UI 又非常的统一:
- 每个输入项占一整列,由上而下依次排列
- label 和输入框都呈上下排列样式
- form 由表单名称、表单内容和提交按钮三部分组成
这种情况下,使用动态表单没有任何问题。但如果需求千变万化,并且每个表单的样式和布局都不一样,那我劝你还是趁早放弃使用动态表单。比如下面这个例子:
从需求上来看,这些表单不属于同一类表单,并且每个表单的布局也不一样。因此,我们很难通过同一套模板去生成这些表单。有的人可能会说,把每个输入项所占的宽度也配置上去不就解决了吗?是的,你的确可以这么做,但并不能从根本上解决这个问题。因为本质上业务场景是不同的,那么这些表单的 UI 、布局和交互很大程度上就会不一致。即便你现在通过增加配置项满足了业务需求,那后期需求发生变化又该怎么办呢?不停地增加配置项吗?因此,不建议在配置里面考虑大量的样式需求。试想如果要通过配置为不同的表单设置不同的样式,那岂不是又强行把配置变成了一套特殊的代码?与其如此,还不如直接写代码!
小结
说了这么多,最后让我们再来总结一下动态表单的适用场景以及选择动态表单应该考虑的问题:
- 动态表单适合拥有大量相似表单需求的场景:输入控件的类型比较固定,UI 和布局相对统一。这样方便定义 Schema,并通过简单循环直接生成表单
- 选择动态表单不应该只从技术角度去决策,还需要考虑业务需求和 UI 设计。不要只为了几行重复的 HTML 代码就选择动态表单方案,要知道有些重复是「必要的」,错误选择动态表单方案反而会降低你的开发效率
- 一旦敲定了动态表单方案,灵活性方面就应该做出一些妥协,不再接受千变万化的 UI 设计。这一点要开发、业务、设计多个角色沟通配合
- 不要通过配置文件配置特别细节的 CSS 样式。你可以封装一些固定的样式,比如两列布局/三列布局,然后通过配置项去切换。