背景
在antd3.x版本中,如果要实现一组表单增加删除的功能,需要Array.map()加上state状态来控制,代码比较复杂,而且非常不优雅。
其次在antd3.x中,表单中任何一个表单项的内容更新都会触发页面重新渲染,这在一个巨型表单页面上简直是灾难。(但是这对于表单联动却是非常方便的,只需要在需要联动的表单前加上判断语句即可)
因此。antd4相对于antd3对表单进行了一些优化,其中就增加了Form.List这个API和shouldUpdate方法。
其中Form.List为字段提供数组化管理,可用于动态增加删除移动表单项。
Form.Item上的shouldUpdate方法用来自定义字段的更新逻辑。
但是其中有一些坑真让人头大
坑1:同时联动多个表单,样式问题
首先,antd3中实现联动的方法在4版本中并不好使,原因就是在antd4中表单内容的更新并不会触发页面的渲染。在antd4中我们有两种方法可以用来控制表单联动,那就是dependencies方法和shouldUpdate方法。
对比了一下官方文档的两种方法,个人感觉
dependencies适用于针对依赖字段重新触发校验逻辑的场景
shouldUpdate适应于针对依赖字段对某一区域进行渲染的场景
所以我们就采用shouldUpdate方法来处理联动。但是当我们实际开发过程中,如果我们是控制一个表单项的联动还好,如果同时控制多个表单项联动,有两种方案
// 方案1:
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => { return prevValues.type !== curValues.type; }} >
{() => {
return <Form.Item label="名字" name={'name'} >
<Input placeholder='请输入' />
</Form.Item>
}}
</Form.Item>
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => { return prevValues.type !== curValues.type; }} >
{() => {
return <Form.Item label="年龄" name={'age'} >
<Input placeholder='请输入' />
</Form.Item>
}}
</Form.Item>
// 方案2:
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => { return prevValues.type !== curValues.type; }} >
{() => {
return <>
<Form.Item label="名字" name={'name'} >
<Input placeholder='请输入' />
</Form.Item>
<Form.Item label="年龄" name={'age'} >
<Input placeholder='请输入' />
</Form.Item>
</>
}}
</Form.Item>
如果采用第一种方案,代码会比较冗余,而且如果表单有多处联动,不太容易区分哪个是联动哪个的,因为是平铺下来的嘛
如果采用第二种方案,会很直观的看出来,某处联动会联动哪几个表单。但是这里有个坑,即使我们设置了noStyle和空标签,被关联的多个表单还是会被一个div包裹起来。这会带来的后果就是,假设一行只有一个固定字段,其他都是被关联的字段,当被关联字段超过了一行能容纳的个数的话会整体换行,导致第一行只留下一个字段,样式布局则会很奇怪。如图
针对这个问题,目前还没想到完美的解决方案,如果有比较好想法的小伙伴,欢迎分享你的想法哟。
坑2:不管是通过dependencies方法还是shouldUpdate方法。当我们通过联动,隐藏某几个表单项后,已收集到form中的数据并不会删除。
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => { return prevValues.type !== curValues.type }} >
{({ getFieldValue }) => {
const type = getFieldValue('type');
if (type === '1') {
return <>
<Form.Item label="名字" name={'name'} >
<Input placeholder='请输入' />
</Form.Item>
<Form.Item label="年龄" name={'age'} >
<Input placeholder='请输入' />
</Form.Item>
</>
} else if (type === '2') {
return <Form.Item label="性别" name={'sex'} >
<Input placeholder='请输入' />
</Form.Item>
}
return null
}}
</Form.Item>
看下面代码,这段代码是当表单中type类型为1时 ,展示名字和年龄字段,当type为2时,展示性别字段
如果我们将type切换成1再切换成2, 表单中会存在名字和年龄,但是在页面上并不存在这两个表单项。
这并不会影响什么,但是个人感觉很不干爽,如果表单变大变复杂,提交表单时也会提交过去一些可能并不需要的字段。因此我们最好删除掉不需要的字段。
这个问题,能够直接想到两种方案。
方案1:在最后提交表单的时候做一下特殊处理。将多余的字段删除后再提交。但是如果在提交前再次回显表单项的时候,还是会回显之前残留的数据,时机太晚。
方案2:在表单联动隐藏的时候,对字段进行重置。考虑使用resetFields,但是经过实践并不好用。可以采用setFieldsValue或setFields对字段进行重置,在如下位置添加相应重置逻辑
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => { return prevValues.type !== curValues.type }} >
{({ getFieldValue }) => {
const type = getFieldValue('type');
if (type === '1') {
{/** 重置表单内容,并初始化名字和年龄 */ }
return <>
<Form.Item label="名字" name={'name'} >
<Input placeholder='请输入' />
</Form.Item>
<Form.Item label="年龄" name={'age'} >
<Input placeholder='请输入' />
</Form.Item>
</>
} else if (type === '2') {
{/** 重置表单内容,并初始化性别 */ }
return <Form.Item label="性别" name={'sex'} >
<Input placeholder='请输入' />
</Form.Item>
}
return null
}}
</Form.Item>
如果表单是固定的话,单独这样写是没啥问题的。
如果我们使用formList来为字段提供数组化管理,动态增减表单项的话。上述方法则会出现一个新的坑。
坑3: formList重新渲染的问题
在上述代码的基础上,如果我们通过formList来包裹的话。正常情况是相互并不干扰的,但是,如果我们使用formList的add或remove方法,则会触发formList的重新渲染,带来的后果则是,重置每一组的表单内容。
解决办法是将重置逻辑写到type表单项的onchange方法中即可