导读
笔者为了提高团队表单开发效率,计划出一套「通用 Form 组件 API」,并以此为基础做到表单开发的配置化。从而解决跨团队、跨技术栈的切换成本问题。
为了更好的理解表单的功能设计,笔者特将国内(比较符合团队使用习惯)流行的 UI 组件库的表单组件 API 设计拿出来对比一下,希望通过比较它们的异同得到一些启发。
正文
API 对比
数据截止 2022.06.09,详见:
Form
| Ant Design | Element UI | Naive UI | 说明 |
|---|---|---|---|
| - colon - labelAlign - labelWrap - labelCol - requiredMark | - label-position - label-width - label-suffix - hide-required-asterisk | - label-width - label-align - label-placement - show-label - show-require-mark - require-mark-placement | label 相关 |
| - layout - wrapperCol | inline | inline | 布局相关 |
| size | size | size | 设置字段组件的尺寸 |
| - scrollToFirstError - validateMessages - validateTrigger | - show-message - inline-message - status-icon - validate-on-rule-change | - show-feedback - validate-messages | 校验信息相关 |
| form(form.xxx) | - validate - validateField - resetFields - clearValidate | - validate - restoreValidation | Form Methods。AntD 是通过 hook 提供 form 对象,上面绑定了很多操作表单的方法,详见 FormInstance。 而其他两者是通过 ref 对象,其实原理差不多 |
| - onFieldsChange - onFinish - onFinishFailed - onValuesChange | - validate | N/A | 各种表单回调事件 |
| initialValues(见说明) | model | model | model 为表单数据对象,可以设置初始值,主要还是用来进行 v-model 的绑定。AntD 的 initialValues 只有初始化值的功能,不具备其他功能 |
| component | ref | ref | AntD:设置 Form 渲染元素,为 false 则不创建 DOM 节点 |
| N/A | rules | rules | 表单验证规则 |
| N/A | disabled | disabled | 是否禁用该表单内的所有组件。若设置为 true,则表单内组件上的 disabled 属性不再生效 |
| fields | N/A | N/A | 通过状态管理(如 redux)控制表单字段,如非强需求不推荐使用。 |
| name | N/A | N/A | 表单名称,会作为表单字段 id 前缀使用 |
| preserve | N/A | N/A | 当字段被删除时保留字段值 |
做一下小结:
- label、布局、校验信息、size 等相关的 UI 类 API 占了绝大多数;
- Form Methods 这种主动操作 Form 的方法也有很多;
- AntD 提供的表单各种生命周期的回调比较丰富,Element 和 Naive 比较欠缺;
- Element 和 Naive 都提供了 Form 级别的校验规则(rules)制定,而 AntD 只有 Field 级别的(见下文)。个人感觉 Field 级别的应该就够用了;
- 特别的,Element 和 Naive 都有 model 属性,笔者特意查了下 Ant Design Vue,也是如此设计的。笔者认为这个属性可以作为区分 React 和 Vue UI 库最明显的特点;
- AntD 提供了配置化的方式生成表单,即 fields 属性。但是官方不推荐,理由见这里。不过笔者倒是觉得用静态变量而不用 store 中的值,应该问题不大。
另外,三个库用的 validator 都是 async-validator,作者 yiminghe 是 ant-design 贡献者,也是 react-component 项目组员。这里笔者有一点疑惑:如果单从数据来看,这个库较其它老牌校验库有一定的差距,详见下图。为什么除了 AntD 之外另两个库也选择了 async-validator 呢?由于还没有深入研究过,所以也不能给出一个主观判断的原因,暂时就将客观数据放在这里,有机会再深入研究下。
Form.Item
| Ant Design | Element UI | Naive UI | 说明 |
|---|---|---|---|
| name | prop | - path - ignore-path-change | 字段名或类似功能 |
| - extra - hasFeedback - help - messageVariables - validateStatus | - error - show-message - inline-message - (slot)error | - feedback - render-feedback - show-feedback - validation-status - (slot)feedback | 校验信息相关 |
| -colon - htmlFor - label - labelAlign - labelCol | - label - label-width - (slot)label | - label - label-align - label-placement - label-style - label-props - label-width - show-label - show-require-mark - require-mark-placement - (slot)label | label 相关 |
| - required - rules - validateFirst - validateTrigger | - required - rules | - first - rule - rule-path | 校验配置相关 |
| - hidden - noStyle - wrapperCol - tooltip | (slot)default | (slot)default | 样式相关 |
| N/A | size | size | 尺寸 |
| N/A | - resetField - clearValidate | - validate - restoreValidation | Field Methods |
| - getValueFromEvent - getValueProps - initialValue - normalize - trigger - valuePropName | N/A | N/A | value 相关 |
| dependencies | N/A | N/A | 设置依赖字段,说明见 dependencies |
| preserve | N/A | N/A | 当字段被删除时保留字段值 |
| shouldUpdate | N/A | N/A | 自定义字段更新逻辑,说明见下 |
小结:
- 关键的,三个库都有一个类似 key 的字段(name、prop、path);
- label、校验信息还有样式相关的 API 还是占了大多数(Naive lable 相关的 API 竟然多达 10 个),感觉还是把它们收敛到 label 和 validateMessage 对象里会更清晰一些;
- Field 级的校验配置 rule(s),三者差不多,没有太多要解释的;
- Element 和 Naive 为 Field 提供了一些 Methods,AntD 虽然没有,但是可以通过 Form 级别的
form.resetFields之类的方法实现; - AntD 提供了很多处理 value 的属性,这点 Element 和 Naive 是缺失的,但是也可以通过对子组件的二次封装,包一层处理 value 的逻辑,也能实现,只是成本高一些;
- 值得一提的是 AntD 提供了一个
dependencies属性,简单说就是在 B 组件上设置了dependencies: [ 'A' ],那么当 A 更新时,B 也会触发更新和校验。个人判断,这个功能对于那种「没有提交按钮的实时保存的表单场景」非常必要。对于普通表单,也就是体验差了一点点的问题(可能只有点提交的时候才能发现 B 的问题)。
Form.List
目前只有 AntD 有这个组件,是专门为 Array 类型的数据提供的,其内部暴露了 add、remove、move 的方法。
之前碰到 Array<Object> 类型的数据,只能通过自定义组件,在组件内部去实现各种数组操作的逻辑,然后主动调用 onChange 方法。有了 Form.List 组件后,实现起来要方便简介许多。
另外,最新版的 AntD 多了很多 Hooks,用法稍微复杂一些,也主要是为了方便一些特殊场景设置的,篇幅关系这里就不展开了。
(2022.06.16 按:在 Naive UI 中也发现了类似功能的组件:动态录入 Dynamic Input)
总结
- 组件分 2 级:Form 和 Field(或 Item),二者的 API 分类比较类似;
- API 分 2 类:UI 样式类和功能类;
- UI 样式类,基本上处理好 layout、label、validateMessage 三方面就够了,其它复杂的布局,就交给自定义组件来兜底;
- 功能类,基本上都可以归到表单状态管理的范畴里。如果用过 React Hook Form、Formik、Final Form 之类的表单状态管理工具,就会发现几乎全部的功能类 API 都能对上;
- AntD 在 Form 级别没有 rules 的配置。这点在逻辑上没有任何问题,可能在某些特定场景需要把聚合的校验配置拆分开,会有点麻烦。不过不算大问题,其实笔者个人是比较支持 AntD 这种设计的,「如非必要勿增实体」嘛。
接下来,笔者就要进行自己的「通用 Form 组件 API」设计了,希望能够出一套框架无关的科学合理的 API。
“完美不在于无以复加,而在于无可删减,万事莫不如此。”
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."——Antoine de Saint-Exupery