在使用 Ant Design Pro Components 的 ModalForm 组件时解决 form.resetFields is not a function 错误
前言
在中台项目中,我们使用了 ant-design/pro-components 库中的 ModalForm 组件。在打开弹窗并处于 loading 状态时,若直接点击关闭按钮,可能会引发以下错误:
form.resetFields is not a function
错误重现
错误分析
错误现象
当我们点击关闭按钮时,弹窗未完全关闭或者表单实例未正确销毁,导致 form.resetFields 方法在表单实例已销毁的情况下被调用,从而触发上述错误。
可能的原因
-
弹窗关闭时表单实例被销毁:弹窗在关闭时,
form.resetFields会尝试访问表单实例,但此时表单实例已经销毁,因此出现错误。 -
异步请求未完成:如果在
request函数的异步操作完成之前关闭了弹窗,可能导致在异步操作完成后,resetFields方法无法正确调用,因为表单实例已销毁。 -
表单实例被替换:如果在外部定义了
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;
错误原因分析
-
异步请求未完成时关闭弹窗: 在
request函数的异步请求还未完成时,如果关闭了弹窗,modalFormRef可能指向的表单实例已经被销毁。异步请求完成后,尝试调用resetFields方法时会报错,因为该表单实例已经不存在。 -
表单实例被销毁: 在
ModalForm的modalProps中使用了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.从控制台报错的地方,点击跳转到源码
2.打上断点,重新运行调式,接着审查元素
2. 本地调试
如果需要,可以在本地 node_modules 中找到相应组件的代码并直接查看。不过这种方法较为繁琐,不是很推荐,特别是对于较大的依赖库。
3. 理解 lib 和 es 目录的区别
- lib 目录:包含已编译的代码,通常是将 TypeScript 或现代 JavaScript 转译为 ES5 版本。
- es 目录:包含使用现代 JavaScript 语法的代码,如 ES6 模块。
如果需要添加调试日志或查看源码,建议分别在这两个目录中进行修改。
总结
虽然这个问题看起来只是一个小的 bug,但它却能帮助我们更深入地理解组件库的内部机制。通过有效的调试和代码分析,我们能够迅速定位并解决问题。此外,这也是一个很好的例子,展示了如何处理异步操作与组件生命周期之间的冲突。
在实际开发中,我们应该注意弹窗组件的销毁和表单实例的管理,避免在异步请求未完成时关闭弹窗,或者在表单实例已经销毁时调用表单方法。
希望这篇文章能给大家提供一些思路,帮助大家在类似的问题中找到解决方案。