为什么antd抽屉组件使用destroyOnClose后form却未被清空?

3,661 阅读3分钟

问题描述

技术栈

React v17.0.2 + AntD v4.20.6

预期结果

使用Antd的抽屉组件(Drawer)+表单组件(From)时,希望关闭抽屉再次打开时,表单中的输入框、下拉框等内容可以被清空。查看了Drawer组件的API信息,发现了destroyOnClose属性可实现该效果。

image.png

当前问题

但是在drawer上添加了该属性后,再次打开抽屉,却发现Form表单没有被清空。

1111.gif

伪代码如下:

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也可以。

image.png

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组件会在一瞬间消失,用户是看不到抽屉组件自带的淡出动画的,因此还是推荐前两种解决方案。