使用 Cursor 修复 Ant Design Pro Components 的 issues
转载自 SegmentFault
Issue: 🐛 ModalFrom在同时设置了formRef、request和destroyOnClose时,加载过程中关闭ModalForm会报错 #8858 PR: https://github.com/ant-design/pro-components/pull/9272
最近有个小伙伴在做稳定性建设,整理了一下现有的线上报错,其中有一个 form.resetFields is not a function 问题分配给我了。
问题之前就我就有发现,后来因为业务比较急,加上问题是组件库引入的就没管,既然给到了我,那我就排查一下,提供了最小复现 Demo证明确实和我们项目的骚代码无关
因为用的也是开源库嘛,我就想着看看是不是也有大兄弟发现了这个问题,果然查到了,然后一看 (opened on Nov 13, 2024) 。
得了,自己修复吧,然后就有了今天这篇记录
代码大师提示词
我们都知道,如果能给 AI 限定一个范围,给定一个明确身份,做明确的一件事情,最好是不管几个 AI或者几个人看到这段文字都能用同一种方式完成这件事,那么就是一个成功的提示词。
你是一位经验丰富的前端代码大师,擅长 React、TypeScript、JavaScript、CSS 前端相关技术。你的任务是帮助我修复开源项目中的 issues,并以最佳实践提交 Pull Request。
这是本次要修复的 issues:https://github.com/ant-design/pro-components/issues/8858
### 角色要求
- 遵循代码整洁之道,风格统一,符合项目规范(ESLint、Prettier、TypeScript 配置等)。
- 提供最小化且精准的改动,避免引入不必要的依赖。
- 在修复问题时,分析 issue 的背景,定位根因,提出多种解决思路,并选出最优解。
- 保持兼容性,避免破坏性修改。
- 严格按照 GitHub 开源协作流程:分析 issue → 编写/更新测试用例 → 本地验证 → 修改代码 → 本地验证 → 编写清晰 commit message → 提交 PR。
- 编写易读的 PR 描述,包括:
1. 问题背景和复现步骤
2. 解决方案及原因
3. 修改点总结
4. 测试结果说明(截图或日志)
### 输出格式要求
- 提供完整的代码 diff 或修复文件。
- 如果需要新建/修改测试,给出 Jest/Vitest 或 Cypress 测试示例。
- 给出 Git commit message(遵循 Conventional Commits)。
- 给出一份标准化 PR 描述(使用中英双语)。
### 注意事项
- 严格遵守仓库的 CONTRIBUTING.md 和 CODE_OF_CONDUCT。
- 如果 issue 涉及性能、可访问性、国际化,需特别指出优化点。
- 任何改动需确保通过已有测试,并尽可能补充新的测试覆盖场景。
代码大师的工作成果
ModalForm 在关闭时安全访问 form.resetFields
说实话,第一个修改我就不喜欢,我要将其修改为下面这种
DrawerForm 也有同样的问题
修改方法同上,这里我非常满意,AI 替我发现另一个相同的错误,不用再次提示
测试用例
学习 AI 是如何完成这件事情
我先查看 GitHub issue 详情来确定复现步骤与根因范围,然后在代码库中并行检索相关实现位置,准备定位问题并编写修复。
您好,针对您提供的 Issue #8858,以下是修复方案及相关信息:
## 问题背景和复现步骤
### 问题描述
在 ModalForm 组件中,同时设置 formRef、request 和 destroyOnClose 属性时,若在数据加载过程中关闭 ModalForm,会抛出错误:“form.resetFields is not a function”。
- 配置 ModalForm,同时设置 formRef、request 和 destroyOnClose 属性。
- 在表单数据加载过程中,关闭 ModalForm。
观察控制台,出现错误:“form.resetFields is not a function”。
解决方案及原因
根本原因:
在 ModalForm 组件中,request 属性用于异步加载表单数据,而 destroyOnClose 属性在关闭时销毁表单。当表单数据尚未加载完成时,关闭表单会导致 form 实例被销毁,从而在调用 resetFields 方法时抛出错误。
解决方案:
在 ModalForm 组件中,添加对 form 实例的存在性检查,确保在调用 resetFields 方法前,form 实例已被正确初始化。
- 我将先在代码库中定位 ModalForm 的实现与关闭时的重置逻辑,特别是与 request、destroyOnClose 交互处,找出调用 resetFields 的位置以确定最小修复点,然后并行检索相关演示与现有测试。
- 我将打开并阅读 GitHub issue #8858 的具体内容与讨论,以确认复现细节和真实报错信息,然后并行检索代码库中 ModalForm 与 DrawerForm 的关闭销毁逻辑与 destroyOnClose/destroyOnHidden 的处理差异,定位根因和最小修复点。
- 我先标记当前任务为“分析并定位 ModalForm 关闭与 resetFields 调用逻辑”,然后并行读取 ModalForm、DrawerForm、BaseForm 相关实现与已有测试,确认 destroyOnHidden 与 resetFields 的触发点,并查找是否存在 destroyOnClose 与 destroyOnHidden 的混用问题。随后我会根据结果决定最小修复方案。
- 我将先更新 todo,将“分析并定位 ModalForm 关闭与 resetFields 调用逻辑”标记完成,并开始“实现最小修复:在关闭时安全访问 form.resetFields”。随后我会直接编辑 ModalForm 和 DrawerForm 的重置逻辑,加上函数存在性判断。完成后再补测试并运行。
- 我将修改 ModalForm 和 DrawerForm 的 resetFields 调用,加上 typeof form.resetFields === ‘function’ 判断,并新增一个针对 request+destroyOnHidden 的关闭场景测试用例。随后运行测试验证。
- 我将对 ModalForm 与 DrawerForm 中的 resetFields 调用增加方法存在性判断,并新增一个模拟 request 加载中关闭的测试用例,随后运行对应测试。
- 我刚把实现与测试都完成并通过了全量用例。现在我会将“撰写 Conventional Commit 与 PR 描述”标记完成,然后给出代码引用、提交信息与 PR 描述。
AI 输出结果
简要对齐
- 你的 Issue 链接指出:ModalForm 同时使用
formRef、request与关闭销毁时(destroyOnHidden/destroyOnClose),在加载中关闭会报错 “form.resetFields is not a function”。 - 代码走查显示,项目内部统一使用
destroyOnHidden(非 antd 原生的 destroyOnClose)控制关闭时销毁。风险点在于关闭时form实例可能尚未就绪或已被销毁,从而直接调用resetFields报错。 - 目标:最小化修复,保证兼容、不引入依赖、通过全部测试。
最小修复点(代码 diff)
- 在
ModalForm与DrawerForm的关闭重置逻辑里,为resetFields增加安全存在性检查,避免在form尚未初始化或已销毁时调用。
代码引用:
const resetFields = useCallback(() => {
const form = rest.form ?? rest.formRef?.current ?? formRef.current;
// 重置表单
if (modalProps?.destroyOnHidden && form && typeof (form as any).resetFields === 'function') {
(form as any).resetFields();
}
}, [modalProps?.destroyOnHidden, rest.form, rest.formRef]);
const resetFields = useCallback(() => {
const form = rest.formRef?.current ?? rest.form ?? formRef.current;
// 重置表单
if (drawerProps?.destroyOnHidden && form && typeof (form as any).resetFields === 'function') {
(form as any).resetFields();
}
}, [drawerProps?.destroyOnHidden, rest.form, rest.formRef]);
测试补充
新增用例:在 ModalForm 使用 request 且 destroyOnHidden: true 时,打开后立即关闭(加载中),不应报错。
代码引用:
it('📦 ModalForm close during request loading with destroyOnHidden should not throw', async () => {
const wrapper = render(
<ModalForm
width={600}
modalProps={{ destroyOnHidden: true }}
request={async () => {
// Simulate slow request
return new Promise((resolve) => setTimeout(() => resolve({ name: 'demo' }), 200));
}}
trigger={<Button id="new">新建</Button>}
>
<ProFormText name="name" />
</ModalForm>,
);
await act(async () => {
const triggerButton = wrapper.getByText('新 建');
fireEvent.click(triggerButton);
});
// Close before request resolves (cancel button may not render while loading), click close icon
await act(async () => {
const closeButton = document.querySelector('button.ant-modal-close');
if (closeButton) {
fireEvent.click(closeButton);
}
});
// Wait a bit for async to flush
await waitForWaitTime(300);
});
- 本地验证:已运行完整
pnpm test,全部通过(71 files, 1563 passed, 1 skipped)。新增用例单测也通过。
解决思路对比与选择
- 方案A:在关闭时调用
resetFields前做方法存在性检查(已选)。最小改动、兼容性好。 - 方案B:在
BaseForm请求完成后再触发重置。副作用大,改变行为时机,风险高。 - 方案C:关闭时取消/中断
request。涉及useFetchData取消逻辑,改动面广。 - 结论:选择方案A,最小化、稳妥。
提交信息(Conventional Commits)
fix(form): prevent error when closing ModalForm/DrawerForm during request with destroyOnHidden
- Guard resetFields call to ensure form instance and method exist
- Cover scenario with a new test: close ModalForm while request is loading
- Keep backward compatibility; no breaking changes
Closes #8858
标准化 PR 描述(中英双语)
问题背景与复现步骤:
使用 ModalForm,同时配置 formRef、request 与 modalProps={{ destroyOnHidden: true }}。
- 打开弹窗后,接口加载尚未返回时立即关闭。
- 控制台出现错误 “form.resetFields is not a function”。
解决方案及原因:
在关闭时重置表单逻辑中,增加 resetFields 方法存在性检查,避免在 form 尚未初始化或已被销毁时调用导致报错。该方案最小化修改、保持既有行为。
修改点总结:
src/form/layouts/ModalForm/index.tsx:关闭重置逻辑加方法存在性判断。
src/form/layouts/DrawerForm/index.tsx:同上。- 新增测试
tests/form/modalForm.test.tsx:覆盖“加载中关闭不报错”的场景。
测试结果: 新增用例通过。
- 全量测试通过:71 files,1563 passed,1 skipped。
English:
- Background & Repro:
- With
ModalForm, configureformRef,request, andmodalProps={{ destroyOnHidden: true }}. - Open the modal and close it before the async
requestresolves. - An error is thrown: “form.resetFields is not a function”.
Solution & Rationale:
Add a guard before calling resetFields to ensure the form instance exists and the method is available. This avoids calling it during loading/unmount phases. Minimal and non-breaking change.
Changes:
Add safe guard in ModalForm and DrawerForm reset logic.
- Add a test to cover closing during request loading.
Test Results: New test passes.
- Full suite passes (71 files, 1563 passed, 1 skipped).
注意点
- 未引入第三方依赖。
- 兼容性良好,不改变 API。
- 与项目
destroyOnHidden语义一致,不混用destroyOnClose。 - 保持原行为:仅在
destroyOnHidden为真时重置表单。
原文:SegmentFault