Stories

Detail Return Return

使用 Cursor 修復 Ant Design Pro Components 的 issues - Stories Detail

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

image.png
説實話,第一個修改我就不喜歡,我要將其修改為下面這種
image.png

DrawerForm 也有同樣的問題

修改方法同上,這裏我非常滿意,AI 替我發現另一個相同的錯誤,不用再次提示

測試用例

image.png

學習 AI 是如何完成這件事情

  1. 我先查看 GitHub issue 詳情來確定復現步驟與根因範圍,然後在代碼庫中並行檢索相關實現位置,準備定位問題並編寫修復。

    您好,針對您提供的 Issue #8858,以下是修復方案及相關信息:
    ## 問題背景和復現步驟
    ### 問題描述
    在 ModalForm 組件中,同時設置 formRef、request 和 destroyOnClose 屬性時,若在數據加載過程中關閉 ModalForm,會拋出錯誤:“form.resetFields is not a function”。
  2. 配置 ModalForm,同時設置 formRef、request 和 destroyOnClose 屬性。
  3. 在表單數據加載過程中,關閉 ModalForm。
  4. 觀察控制枱,出現錯誤:“form.resetFields is not a function”。

    解決方案及原因

    根本原因:

    在 ModalForm 組件中,request 屬性用於異步加載表單數據,而 destroyOnClose 屬性在關閉時銷燬表單。當表單數據尚未加載完成時,關閉表單會導致 form 實例被銷燬,從而在調用 resetFields 方法時拋出錯誤。

    解決方案:

    在 ModalForm 組件中,添加對 form 實例的存在性檢查,確保在調用 resetFields 方法前,form 實例已被正確初始化。

  5. 我將先在代碼庫中定位 ModalForm 的實現與關閉時的重置邏輯,特別是與 request、destroyOnClose 交互處,找出調用 resetFields 的位置以確定最小修復點,然後並行檢索相關演示與現有測試。
  6. 我將打開並閲讀 GitHub issue #8858 的具體內容與討論,以確認復現細節和真實報錯信息,然後並行檢索代碼庫中 ModalForm 與 DrawerForm 的關閉銷燬邏輯與 destroyOnClose/destroyOnHidden 的處理差異,定位根因和最小修復點。
  7. 我先標記當前任務為“分析並定位 ModalForm 關閉與 resetFields 調用邏輯”,然後並行讀取 ModalForm、DrawerForm、BaseForm 相關實現與已有測試,確認 destroyOnHidden 與 resetFields 的觸發點,並查找是否存在 destroyOnClose 與 destroyOnHidden 的混用問題。隨後我會根據結果決定最小修複方案。
  8. 我將先更新 todo,將“分析並定位 ModalForm 關閉與 resetFields 調用邏輯”標記完成,並開始“實現最小修復:在關閉時安全訪問 form.resetFields”。隨後我會直接編輯 ModalForm 和 DrawerForm 的重置邏輯,加上函數存在性判斷。完成後再補測試並運行。
  9. 我將修改 ModalForm 和 DrawerForm 的 resetFields 調用,加上 typeof form.resetFields === 'function' 判斷,並新增一個針對 request+destroyOnHidden 的關閉場景測試用例。隨後運行測試驗證。
  10. 我將對 ModalForm 與 DrawerForm 中的 resetFields 調用增加方法存在性判斷,並新增一個模擬 request 加載中關閉的測試用例,隨後運行對應測試。
  11. 我剛把實現與測試都完成並通過了全量用例。現在我會將“撰寫 Conventional Commit 與 PR 描述”標記完成,然後給出代碼引用、提交信息與 PR 描述。

AI 輸出結果

簡要對齊

  • 你的 Issue 鏈接指出:ModalForm 同時使用 formRefrequest 與關閉銷燬時(destroyOnHidden/destroyOnClose),在加載中關閉會報錯 “form.resetFields is not a function”。
  • 代碼走查顯示,項目內部統一使用 destroyOnHidden(非 antd 原生的 destroyOnClose)控制關閉時銷燬。風險點在於關閉時 form 實例可能尚未就緒或已被銷燬,從而直接調用 resetFields 報錯。
  • 目標:最小化修復,保證兼容、不引入依賴、通過全部測試。

最小修復點(代碼 diff)

  • ModalFormDrawerForm 的關閉重置邏輯裏,為 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 使用 requestdestroyOnHidden: 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 描述(中英雙語)

  • 問題背景與復現步驟:

    1. 使用 ModalForm,同時配置 formRefrequestmodalProps={{ destroyOnHidden: true }}
    2. 打開彈窗後,接口加載尚未返回時立即關閉。
    3. 控制枱出現錯誤 “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:
    1) With ModalForm, configure formRef, request, and modalProps={{ destroyOnHidden: true }}.
    2) Open the modal and close it before the async request resolves.
    3) 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 為真時重置表單。
user avatar tianmiaogongzuoshi_5ca47d59bef41 Avatar cyzf Avatar alibabawenyujishu Avatar zaotalk Avatar yinzhixiaxue Avatar jcguanqi Avatar freeman_tian Avatar qingzhan Avatar dirackeeko Avatar littlelyon Avatar razyliang Avatar linx Avatar
Favorites 165 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.