持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
背景与需求场景
在有的项目场景下,我们需要在表单结构内部快速添加或删除一项与某一项结构完全相同的部分,此时可以使用动态可增减表单项,有时我们只想增加或减少某一区域内部的某一子项,此时可以嵌套使用动态可增减表单项来实现。如下效果:
antd的Form.List基本API
要想实现嵌套可动态增减表单项的效果,必须熟悉Form.List,它为字段提供了数组化管理,它包含四个属性,分别为:
- children: 它是一个渲染函数,形如
(fields: Field[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode
<Form>
<Form.List
name="names">
{(fields, { add, remove }, { errors }) => {
return (
<>
{fields.map((field, index) => (
<></>
))}
<Form.Item>
<Form.ErrorList errors={errors} />
</Form.Item>
</>
);
}}
</Form.List>
</Form>
该渲染函数有三个参数:
- 第一个参数为
fields,是一个Field类型的数组,Field类型为一个对象包括name、key、isListField、fieldKey字段。 - 第二个参数为渲染表单相关操作函数:包括
add(defaultValue?: any, insertIndex?: number)方法,表示新增表单项,add方法参数可用于设置初始值;remove(index: number | number[])方法,表示删除表单项;move(from: number, to: number)方法,表示移动表单项。 - 第三个参数为
errors:Form.ErrorList时错误展示组件,仅限配合Form.List的rules一同使用
<Form.ErrorList errors={errors} />
示例
如下图,增加了三项,则fields为一个包含三个Field类型对象的数组,其fields字段数据为:
fields:
所以要想在页面上渲染出这些动态增加的表单项,只需要使用
map方法循环fields即可,但需要注意的是,由于使用了map方法,所以其内部的每一个表单项Form.Item都需要key,key值可以设置为每个field的field.key字段。
{fields.map((field, index) => (
<Form.Item
label={index === 0 ? 'Passengers' : ''}
required={false}
key={field.key}
>
<Form.Item
{...field}
rules={[
{
required: true,
message: "Please input name",
},
]}
noStyle
>
<Input placeholder="passenger name" style={{ width: '60%' }} />
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(field.name)}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
style={{ width: "60%" }}
icon={<PlusOutlined />}
>
Add field
</Button>
</Form.Item>
以上代码,我们能够注意到,每一项Form.Item内层都还有一层Form.Item包裹,但是,Antd只会以其内部直接跟有input或select等表单项的Form.Item为准。所以可以在内部的Form.Item中解构出field作为其属性{...field}。
对于每一项,当除了只有一项时,其他每项fields.length > 1时我们都会添加减号,并给它添加onClick方法,点击减号,就调用Form.List提供的删除表单项方法remove,并传入需要删除项的name属性onClick={() => remove(field.name)}。
实现双重嵌套可动态增减功能
嵌套即不单单只是一个Form.List了,而是两个或多个Form.List嵌套使用,并且每一层Form.List的Field都需要使用map方法,内部的Form.List需要在外部Form.List的field循环时使用。
嵌套Form.List的大致框架代码为:
const FormList = Form.List;
const FormItem = Form.Item;
const TwoFormList: FC<{}> = () => {
return (
<Form form={form}>
<FormList name={['list1']}>
{(level1Fields, { add, remove }, { errors }) => {
const level1Remove = remove;
return (
<div>
{level1Fields.map((level1Field) => (
<FormList
name={[level1Field.name, 'level2List']}
key={level1Field.key}
initialValue={[{ isListValue: true, },]}
>
{(
level2Fields = [{ name: 0, key: 0, fieldKey: 0 }],
{ add, remove },
{ errors }
) => {
return (
<div>
{level2Fields.map((level2Field, fIdx: number) => (
<div key={'_' + level2Field}>
<FormItem name={[level2Field.name, 'item1']} noStyle>
<Select> .... </Select>
</FormItem>
<FormItem name={[level2Field.name, 'item2']} noStyle>
<Input />
</FormItem>
</div>
))}
<Button onClick={() => { add(); }}>
内部+
</Button>
<Form.ErrorList errors={errors} />
</div>
);
}}
</FormList>
))}
<Button onClick={() => { add();form.setFieldsValue({}); } }>
外部+
</Button>
</div>
);
}}
</FormList>
</Form>
);
};
总结
- Form表单,Form.List的嵌套使用时,一般需要将从接口获取到的数据格式化为表单能够展示的形式,在点击保存按钮时,需要将表单数据格式化为接口参数的形式,以便于传递到接口的入参。
- Form.List中提供namepath获取的数据,一般有多个,是以数组形式返回的
- <Form.Item name="field" /> 只会对它的直接子元素绑定表单功能,例如直接包裹了 Input/Select组件。如果控件前后还有一些文案或样式,或者一个表单项内有多个控件,可以使用内嵌的Form.Item完成。若添加noStyle属性,则表示不使用其默认样式。