问题描述
技术栈
React v17.0.2 + AntD v4.20.6
预期结果
使用Antd的抽屉组件(Drawer)+表单组件(From)时,希望关闭抽屉再次打开时,表单中的输入框、下拉框等内容可以被清空。查看了Drawer组件的API信息,发现了destroyOnClose属性可实现该效果。
当前问题
但是在drawer上添加了该属性后,再次打开抽屉,却发现Form表单没有被清空。
伪代码如下:
import React,{ useState } from 'react'
import { Button, Drawer, Form, Input, Space, Modal } from 'antd'
const Demo = () => {
const [form] = Form.useForm(); // 使用useForm控制表单数据域
const [visible, setVisible] = useState<boolean>(false)
return <>
<Button type='primary' onClick={()=>setVisible(true)}>
打开抽屉
</Button>
<Drawer destroyOnClose visible={visible} onClose={()=>setVisible(false)}>
<Form form={form}>
<Form.Item label="名称" name="text">
<Input placeholder="请输入..." />
</Form.Item>
</Form>
</Drawer>
</>
}
解题思路
以前使用ref的方式控制表单的数据域时,在Drawer上添加destroyOnClose属性,可以实现再次打开抽屉,表单即被清空。现在使用了antd官网推荐的 Form.useForm 来控制表单的数据域,destroyOnClose属性就是失效了,会不会是使用useForm导致的问题呢?
// formRef + destroyOnClose 再次打开抽屉,可以清空表单
const DrawerDemo1 = () => {
formRef = React.createRef<FormInstance>();
return (
<Drawer destroyOnClose>
<Form ref={this.formRef}>xxxx</Form>
</Drawer>
)}
// useForm + destroyOnClose 再次打开抽屉,无法清空表单
const DrawerDemo2 = () => {
const [form] = Form.useForm();
return (
<Drawer destroyOnClose>
<Form form={form}>xxxx</Form>
</Drawer>
)}
ref方式可以在类组件、函数式组件中使用,但是useFrom方式因为是hooks的写法,因此只能在函数式组件上使用,两个demo中使用的都是函数式组件,在使用方法上没有错误。
排查了下DOM元素,添加了destroyOnClose后,两个demo中的在关闭抽屉后,Drawer body dom也已删除。
到底是哪里出了问题呢?看了源码后了解到:
使用AntD组件时,原理都是通过各种组件的类去new一个实例出来,使用ref的时候Form的实例是Form组件自己管理的,destroyOnClose后Form都销毁了,它的Form的实例当然也被销毁了,因此表单就被清空了。而通过useForm创建的Form的实例是在Form组件之外,不由Form管理,关闭抽屉后Form的实例还活着,未被销毁,所以即使添加了destroyOnClose属性也没用。
那现在问题就转变为:使用Drawer + Form + useFrom时,如何在关闭抽屉后清空表单?
解决办法
方法一:使用Form的resetFields方法手动清空表单
在抽屉所在组件中使用useEffect监听Drawer的visible属性,在visible=true即抽屉被打开时,执行resetFields手动清空表单,这样就可以保证抽屉再次打开时是清空状态了。
const Demo = () => {
const [form] = Form.useForm();
const [visible, setVisible] = useState<boolean>(false)
useEffect(() => {
if(visible) {
form.resetFields() // 手动清空表单
}
}, [visible])
return <>
<Button type='primary' onClick={()=>setVisible(true)}>
打开抽屉
</Button>
<Drawer destroyOnClose visible={visible} onClose={()=>setVisible(false)}>
<Form form={form}>
<Form.Item>xxx</Form.Item>
</Form>
</Drawer>
</>
方法二:使用Form的preserve属性清空表单
排查问题的过程中,意外发现了AntD的Modal组件的文档中恰好提到了跟本文同样的问题。Form组件的 preserve 属性也可做到清空表单,不用添加额外的useEffect也可以。
const Demo = () => {
const [form] = Form.useForm();
return (
<Drawer>
<Form form={form} preserve={false}>
<Form.Item>xxx</Form.Item>
</Form>
</Drawer>
)
方法三:使用visible && < Drawer / >控制drawer的显隐
当visible=true时,Drawer组件显示;当visible=false时,Drawer组件消失。
// 父组件
const Father = () => {
const [visible, setVisible] = useState<boolean>(false)
return <>
<Button type='primary' onClick={()=>setVisible(true)}>
打开抽屉
</Button>
{visible && <Son visible={visible} onClose={()=>setVisible(false)}>}
</>
}
// 子组件
const Son = () => {
const { visible, onClose } = props;
const [form] = Form.useForm();
return <Drawer destroyOnClose visible={visible} onClose={onClose}>
<Form form={form}>
<Form.Item>xxx</Form.Item>
</Form>
</Drawer>
【缺点】visible=false时,Drawer组件会在一瞬间消失,用户是看不到抽屉组件自带的淡出动画的,因此还是推荐前两种解决方案。