在使用ModalForm组件时解决 form.resetFields is not a function 错误

969 阅读4分钟

在使用 Ant Design Pro Components 的 ModalForm 组件时解决 form.resetFields is not a function 错误

前言

在中台项目中,我们使用了 ant-design/pro-components 库中的 ModalForm 组件。在打开弹窗并处于 loading 状态时,若直接点击关闭按钮,可能会引发以下错误:

form.resetFields is not a function

错误重现

  1. image.png

  2. image.png

错误分析

错误现象

当我们点击关闭按钮时,弹窗未完全关闭或者表单实例未正确销毁,导致 form.resetFields 方法在表单实例已销毁的情况下被调用,从而触发上述错误。

可能的原因

  1. 弹窗关闭时表单实例被销毁:弹窗在关闭时,form.resetFields 会尝试访问表单实例,但此时表单实例已经销毁,因此出现错误。

  2. 异步请求未完成:如果在 request 函数的异步操作完成之前关闭了弹窗,可能导致在异步操作完成后,resetFields 方法无法正确调用,因为表单实例已销毁。

  3. 表单实例被替换:如果在外部定义了 formRef 并替换了内部的表单实例,也可能导致 formRef 指向的对象无效。

代码示例

示例代码

import { detailMaterialActivities } from "@/services/xxx";
import type { ProFormInstance } from "@ant-design/pro-components";
import { ModalForm } from "@ant-design/pro-components";
import React, {
  forwardRef,
  memo,
  useImperativeHandle,
  useRef,
  useState,
} from "react";

const DetailModal = memo(
  forwardRef((props: any, ref: any) => {
    const modalFormRef = useRef<ProFormInstance>();
    const [modalVisible, setModalVisible] = useState<boolean>(false);

    useImperativeHandle(ref, () => {
      return {
        setVisit: (visible: boolean, value: any) => {
          setModalVisible(visible);
        },
      };
    });

    return (
      <ModalForm
        width={650}
        layout="horizontal"
        title={`查看详情`}
        open={modalVisible}
        formRef={modalFormRef}
        modalProps={{
          destroyOnClose: false,  // 可能是问题所在
          maskClosable: false,
          footer: null,
        }}
        submitTimeout={2000}
        request={async () => {
          const { data } = await detailMaterialActivities({
            id: detailId,
          });

          return data;
        }}
      >
        <>组件业务实现</>
      </ModalForm>
    );
  })
);

export default DetailModal;

错误原因分析

  1. 异步请求未完成时关闭弹窗: 在 request 函数的异步请求还未完成时,如果关闭了弹窗,modalFormRef 可能指向的表单实例已经被销毁。异步请求完成后,尝试调用 resetFields 方法时会报错,因为该表单实例已经不存在。

  2. 表单实例被销毁: 在 ModalFormmodalProps 中使用了 destroyOnClose: false,虽然这个属性使得弹窗不会被销毁,但如果表单实例没有正确维护或被替换,仍然可能会出现 form.resetFields is not a function 的错误。

解决方案

1. 去除 formRef 的引用

如果不需要使用 ref 对象,可以直接删除 formRef={modalFormRef},避免表单实例的引用可能出现问题。

<ModalForm
  width={650}
  layout="horizontal"
  title={`查看详情`}
  open={modalVisible}
  modalProps={{
    destroyOnClose: false,  // 这里保持不变
    maskClosable: false,
    footer: null,
  }}
  submitTimeout={2000}
  request={async () => {
    const { data } = await detailMaterialActivities({
      id: detailId,
    });

    return data;
  }}
>
  <>组件业务实现</>
</ModalForm>

2. 去除 destroyOnClose: false 配置

如果不需要保留整个弹窗实例,可以尝试去除 destroyOnClose: false,让弹窗在关闭时销毁。这可以确保表单实例不会在弹窗关闭后变为无效。

<ModalForm
  width={650}
  layout="horizontal"
  title={`查看详情`}
  open={modalVisible}
  modalProps={{
    maskClosable: false,
    footer: null,
  }}
  submitTimeout={2000}
  request={async () => {
    const { data } = await detailMaterialActivities({
      id: detailId,
    });

    return data;
  }}
>
  <>组件业务实现</>
</ModalForm>

3. 保持弹窗实例和表单实例一致

如果必须使用 destroyOnClose: false,确保表单实例在弹窗关闭时仍然有效。可以在异步请求完成后再处理表单重置。

4. 通过代码审查和调试解决

通过在开发工具中调试代码并查看控制台错误,可以帮助我们快速定位问题。若问题出现在第三方库中,考虑通过 PR 提交修复。

源码分析

在解决类似问题时,我们可以通过以下几种方式进行源码分析:

1. 使用浏览器开发者工具查看错误堆栈

1.从控制台报错的地方,点击跳转到源码

image.png

2.打上断点,重新运行调式,接着审查元素

image.png

2. 本地调试

如果需要,可以在本地 node_modules 中找到相应组件的代码并直接查看。不过这种方法较为繁琐,不是很推荐,特别是对于较大的依赖库。

image.png

3. 理解 libes 目录的区别

  • lib 目录:包含已编译的代码,通常是将 TypeScript 或现代 JavaScript 转译为 ES5 版本。
  • es 目录:包含使用现代 JavaScript 语法的代码,如 ES6 模块。

如果需要添加调试日志或查看源码,建议分别在这两个目录中进行修改。

总结

虽然这个问题看起来只是一个小的 bug,但它却能帮助我们更深入地理解组件库的内部机制。通过有效的调试和代码分析,我们能够迅速定位并解决问题。此外,这也是一个很好的例子,展示了如何处理异步操作与组件生命周期之间的冲突。

在实际开发中,我们应该注意弹窗组件的销毁和表单实例的管理,避免在异步请求未完成时关闭弹窗,或者在表单实例已经销毁时调用表单方法。

希望这篇文章能给大家提供一些思路,帮助大家在类似的问题中找到解决方案。