虽然之前对业务框架的常用组件做了一些介绍,也在实践中与大家沟通过组件的使用方法,但还没有体系化地介绍常见的使用场景,这样就难免会出现一些误用,也对后期的维护工作带来了不少困难。
默认值
默认值就是一个很简单的值,它不能依赖formModel
相关的值。
// 简单值
{
defaultValue: 1
}
// 来源于一个其他的变量,可以是普通变量,也可以是 ref 变量。
const a = 1;
{
defaultValue: a
}
// 或者
const a = ref(1);
{
defaultValue: a.value
}
// 不可以依赖formModel相关的值
{
defaultValue: proFormRef.value.formModel.b
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
总之,就是简单点,别搞花里胡哨的。
这个默认值也将作为dataIndex
所关联的字段的初始值。
表单回写
表单一旦初始化完毕后,它可以自行运转,不需要人为干预。
但是有时候,我们会在一些操作后,希望改变某个表单控件的值,这个时候,需要用到 writeBackForm
来控制,不要尝试用其他自己想出来的方法去更新表单,不一定产生合理的效果。
// 可以实现效果,但不是很建议
proFormRef.value.formModel.someField = 1;
2
writeBackForm
可以一开始就有值,也可以在异步后得到值。可以是ref
,也可以是reactive
。
// 使用 ref
const writeBackForm = ref();
// 异步后整体赋值
writeBackForm.value = res.result
// 使用 reactive
const writeBackForm = reactive({});
// 异步后更新
// 只更新一个字段
writeBackForm.a = res.result.a
// 批量更新
Object.assign(writeBackForm, res.result)
2
3
4
5
6
7
8
9
10
11
12
通过 writeBackForm
回写表单,能保证正确地处理一些特殊值,比如范围值。
对于日期范围,我们从 detail 接口拿到的字段可能是 startTime
, endTime
,而实际上 daterange
组件需要的是一个数组值,可理解为[startTime, endTime]
。
借助 writeBackForm
,我们不需要特意处理范围数据,因为组件内部可以通过transformPropsForRange
自动检测和更新数据,保证数据和 UI 的一致性。
你还可以通过
setField(field, value)
或者setFields(model)
进行单一或者批量的表单更新,但是它不会特意去处理特殊类型的表单项,比如前面提到的日期范围。
表单之外...
实际业务中,除了表单 UI 上体现的字段,还有一些字段我们也需要处理,最后传给后端。
对于这些,我们分两个视角来看。
- 固定不变,雷打不动的,见
fixedForm
。 - 可能有逻辑,随逻辑变化的,见
beforeSearch
。
fixedForm
对于一些确定下来的参数,并且又不需要展示在表单上的,我们可以通过 fixedForm
指定。
fixedForm
一定是简单的,它就像默认值一般,是可以直接确定下来的。如果你写的 fixedForm
很复杂,有很多依赖,那你可能要思考一下了。
比如后端固定要求,type
传 5,xxx
传 6。不用想了,放在fixedForm
很合适。
beforeSearch
对于一些存在逻辑关系的数据,我们需要用到beforeSearch
来处理,最后返回一个完整的对象,交由page
接口使用。
比如行政区域组件,组件绑定的值是一个数组,然而后端可能是需要provinceId
,cityId
之类的字段。这个时候放在 beforeSearch
是最合适的。
const beforeSearch = (formModel) => {
return {
...formModel,
provinceId: formModel.cantonIds[0],
cityId: formModel.cantonIds[1]
}
}
2
3
4
5
6
7
很多人会习惯,在onChange
马上把数据处理好。实际上,onChange
可能会发生很多次,是不是每次onChange
后,你都需要得到处理完后的数据呢?大部分情况下,不需要。
你需要这些处理完的数据的时机,往往都是在提交表单,或者提交查询条件前。所以,只需要在提交之前通过beforeSearch
处理即可。
提前处理数据还会让你的代码定义很多不必要的变量,维护起来非常麻烦。
总之,让表单自己运转吧,不要去干涉它。你想要结果的时候,再去问它要。
表单提交
正如表格场景的beforeSearch
所述,表单ProForm
也可能在提交前需要处理数据,这个可以通在onSubmit
中处理,算是比较常见的。
const onSubmit = async (formModel) => {
const params = {
...formModel,
otherField: formModel.a + formModel.b
}
await xxxService.yyyMethod(params);
}
2
3
4
5
6
7
自定义渲染
渲染的范围
首先,使用customRenderFormItem
可以自定义渲染某个表单项,配合renderFormItemLevel
可以实现不同颗粒度的渲染。 renderFormItemLevel
有两个备选值,分别是formItem
和widget
,默认值是widget
。两者的渲染范围不同,具体理解见下面图示。
渲染的内容
自定义渲染的意思就是不限制你要画什么组件,按钮,输入框,div,随你便。
但是如果你要绘制与表单数据有关的东西,比如由两个输入框组成的一个范围输入。从数据层面看,实际上它应该是formModel
的一部分,只不过是你自己自定义渲染的,所以它的输入(修改)依然应该反馈到formModel
上,那么怎么做呢?
有人可能会想到自己定义两个 ref ,配合 onChange 存一下这两个输入框的值。最后提交表单的时候再把这两个值传过去。
方式没有问题,但不够优雅。实际上,组件设计已经考虑了这个问题,我们可以通过customRenderFormItem
拿到formModel
对象,对formModel
直接进行操作。我这里直接拿示例组件说一下,一个输入框和两个输入框本质上没有区别。同理,下拉框,其他的都是一个道理,理论上不必定义额外的变量来临时存储(很复杂的除外)。
这种做法有点类似于 React 的受控组件/非受控组件。受控的意思是框架内部已经完成了数据逻辑(绑定值和更新值)的流转。非受控的意思是你放弃了框架内部的逻辑,准备自己动手接管,这个时候你就要自行完成绑定值和更新值的操作。
或者说跟驾车也类似,自定义渲染的默认行为相当于司机已经松开了方向盘,如果你不接管,车开到哪里就不好说了(也就是:这个数据和表单不一定是正确的),所以你必须接管!