Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

一次说清,为什么在Antd Modal中调resetFields调了个寂寞 #84

Open
closertb opened this issue Dec 23, 2021 · 0 comments
Open

Comments

@closertb
Copy link
Owner

背景

在干了大半年增删查改后(node,mysql,serverless),业务端人手短缺,老板开恩让我支持其他团队写几个页面。

久了不摸手生,除了react依稀记得,antd基本只能看着官方demo一行一行写,感觉一天能写完的,结果两天了还没联调完。中间还遇到一些似曾相识的问题,可惜以前的经验已经不管用了。

demo地址: https://codesandbox.io/s/antd-about-resetfields-tjcns

这些问题在antd的仓库issue都反复被提及,看了下文,包括但不限于以下问题都将得到答案:
20211221224910

概括一下:

  • Form表单,React hooks 组件,initialValues初始化数据时候,第二次、第三次……传递新值,表单没有更新,永远显示第一次数据?
  • 弹出层新建表单重新设置值不起作用?
  • Modal 用了destroyOnClose,里面有 Form,并使用 form.resetFields,为什么会失效?
  • Modal中initialValues更新了,使用了form.resetFields,要连续打开两次才生效?

有事说事

语言描述显得太苍白,所以直接看动图吧:
reset-small

这是一个简单的增删查改页面,新增和编辑共享了同一个组件,期望在打开弹窗编辑表单关闭后,重新打开时,能根据initialValues重新渲染表单, 但得到的结果是,第二次打开,编辑框没有刷新.

实现的伪代码大致是这样:

import React, { useEffect } from "react";
import { Modal, Form, Input, Button, Checkbox } from "antd";

export function EditModal(props) {
  const { visible, onOk, onCancel, content = {} } = props;
  const [form] = Form.useForm();
  const isEdit = !!content.sort;
  const handlSubmit = (close) => {
    // 一些提交逻辑
  };
  useEffect(() => {
    // setTimeout(() => {
    form.resetFields();
    // });
  }, [content]);

  return (
    <Modal
      title={`${isEdit ? "编辑" : "新建"}备注`}
      visible={visible}
      destroyOnClose
      onOk={onOk}
      onCancel={onCancel}
    >
      <Form
        name="basic"
        labelCol={{ span: 7 }}
        wrapperCol={{ span: 14 }}
        form={form}
        initialValues={content}
        autoComplete="off"
      >
        {...一些表单}
      </Form>
    </Modal>
  );
}

相信出现问题的盆友们,大多都是和我一样,如上面这样的代码这样实现。

具体问题,具体分析

先给结论,之所以会出现上面的那些问题,主要是三个问题导致:

  • react hooks使用姿势不正确,antd4 form引入了hooks, 和antd3使用有所区别;
  • 对form表单initialValues的认识不清;
  • Modal子元素的渲染是异步的,destroyOnClose 错误使用;

initialValues初始化数据时候,第二次、第三次……传递新值,表单没有更新?

因为initialValues只在表单首次初始化时有效,只要表单没有卸载并重新挂载,改变initialValues都不会刷新表单的值,form最初的设计就如此;以下是initialValues初始化存到store的完整实现:

  this.setInitialValues = function (initialValues, init) {
    _this.initialValues = initialValues || {};
    if (init) {
      // setValues 作用类似于Object.assign();
      _this.store = setValues({}, initialValues, _this.store);
    }
  };

this.store 是存放在form实例中的,只要实例不销毁,store的值就不会变化。

destroyOnClose,弹出层新建表单重新设置值不起作用?

首先这里有个概念,initialValues 在Form表单实例挂载时,这个值是被存在了用hooks生成的form实例中。

所以当我们使用了destroyOnClose,虽然销毁了Modal 以及Modal框中的Form,但这个form实例仍然存在,这个hook实例是挂载在EditModal元素上的,并没有被一起销毁,所以当弹窗再次打开,Form表单又会根据这个form的store再次渲染(原因见上)。

Modal 用了destroyOnClose,里面有 Form,并使用 form.resetFields,为什么会失效?

当我们意识到form实例没有被销毁,可能保存了上一个表单编辑状态时,我们会想到使用useEffect钩子,去观察初始值,采用form.resetFields去重置实例,但最后发现这并没有起作用(我也踩到了这个坑上)。

当我去掉destroyOnClose,我发现生效了,后面我去看了一下form.resetFields的实现源码:

this.resetFields = function (nameList) {
  var prevStore = _this.store;

  if (!nameList) {
    // console.log(JSON.stringify(prevStore), JSON.stringify(_this.initialValues));
    _this.store = setValues({}, _this.initialValues);
    _this.resetWithFieldInitialValue();
    _this.notifyObservers(prevStore, null, {
      type: 'reset'
    });
    return;
  }
}

这个实现和initialValues 一样简单明了,所以问题不在resetFields。问题是出在Modal身上,简单来讲Moda的创建有一个异步过程,所以子组件的渲染并不是同步的。正常的组件渲染是下面这样的:

20211223222510

只需和我上面一样,在resetFields加一句console, 就会发现_this.initialValues是上一次的初始值,而不是新传入的(因为Form元素还未挂载),所以这里resetFields调了个寂寞。

还有一种简单的方法证明Modal组件的子组件挂载是异步的,就是如下面这样去玩:

useEffect(() => {
  setTimeout(() => {
    form.resetFields();
  });
}, [content]);

这个实现,你会发现resetFields居然生效了,因为一个宏任务后,Form元素已经挂载上。

所以这里告诉我们,要尽量少用destroyOnClose,因为Modal的渲染是耗时的且费力的。

Modal使用了form.resetFields初始化,要连续打开两次才生效?

相信经过上面的一系列解释,你的心中已经有了答案;destroyOnClose 确实不适合在Modal中写表单时用。

吃一堑,长一智

这一次经历后,我记住了:

  • destroyOnClose要慎用,因为Modal的渲染是昂贵的;
  • hooks 是个好东西,但你得用对;
  • antd是个好东西,前提是你会用;
  • 我还是太菜了;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant