# 注册表单状态清理 - Bug分析与修复 ## Bug 描述 **问题场景**: 1. 第一次填写完整的患者信息并注册成功 2. 表单UI清空,但内部状态未清空 3. 第二次不填写任何信息,直接选择体位后点击注册 4. ❌ Bug:仍然可以注册成功(应该失败) --- ## 根本原因分析 ### 涉及的领域模型概念 这个bug涉及到**两个独立的状态管理域**: #### 1. Ant Design Form State(组件级状态) ```typescript const [form] = Form.useForm(); // 内部维护表单字段值 // 生命周期:从组件创建到手动重置 // 清理方式:form.resetFields() ``` **特性**: - 状态持久化:即使UI不显示,内部store仍然保留数据 - 独立管理:不受Redux状态影响 - 需要显式清理:必须调用`form.resetFields()` #### 2. Redux formSlice State(全局状态) ```typescript interface FormState { formData: Record; } // 存储表单数据的副本 // 用于跨组件共享 ``` **特性**: - 全局可访问 - 需要通过action更新 - 需要显式清理action ### 因果链条(清晰明确) ``` 第一次注册成功 ↓ 触发事件:registerWork API 返回 code === '0x000000' ↓ 当前行为(Bug原因): - ✅ Redux viewSelection.selectedViews 被清空 - ❌ Redux formSlice.formData 未清空 - ❌ Ant Design Form 内部状态未清空 ↓ 状态不一致: - UI层:表单显示为空(用户视角) - 数据层:form.getFieldsValue() 返回旧数据(系统视角) ↓ 第二次注册 ↓ 用户操作:只选择体位,不填表单 ↓ 点击"注册"按钮 ↓ handleRegister() 执行: 1. let values = form.getFieldsValue(); // ❌ 返回第一次的完整数据(用户不知道) 2. validateResult = schema.safeParse(values); // ✅ 验证通过(因为values有完整数据) 3. 调用 registerWork(registerInfo); // ❌ 使用旧数据注册成功 ↓ Bug结果:用户以为表单是空的,实际用旧数据注册成功 ``` --- ## 修复方案 ### 方案概述 在两个关键时机清理**双重状态**: 1. **清理时机1**:注册成功后(最重要) 2. **清理时机3**:组件卸载时(防御性) ### 实现细节 #### 修改1:formSlice 添加清理action **文件**:`src/states/patient/register/formSlice.ts` ```typescript const formSlice = createSlice({ name: 'form', initialState, reducers: { setFormData: (state, action) => { state.formData = action.payload; }, // ✅ 新增:清空表单数据 clearFormData: (state) => { state.formData = {}; console.log('Redux formSlice: 表单数据已清空'); }, }, }); export const { setFormData, clearFormData } = formSlice.actions; ``` **领域概念**: - **Action**:clearFormData - 表达"清空表单"的业务意图 - **Reducer**:将状态重置为初始值 `{}` - **日志**:记录状态变化,便于调试 #### 修改2:注册成功后清理(清理时机1) **文件**:`src/pages/patient/Register.tsx` ```typescript const response = await registerWork(registerInfo); if (response.code !== '0x000000') { message.error(`注册失败: ${response.description}`); return { success: false, views: [] }; } // ✅ 注册成功后的三重清理 // 1. 清理 Redux formSlice dispatch(clearFormData()); // 2. 清理 Ant Design Form form.resetFields(); // 3. selectedViews 已在 viewSelectionSlice 中自动清空 console.log('注册成功,表单已清空,可以开始新的注册'); return { success: true, data: response.data, views: selectedViews }; ``` **因果关系**: ``` API返回成功 ↓ 触发清理三重奏: 1. dispatch(clearFormData()) → Redux formSlice.formData = {} 2. form.resetFields() → Ant Design Form 内部状态清空 3. selectedViews自动清空 → 已在viewSelectionSlice实现 ↓ 系统恢复初始状态 ↓ 用户可以开始新的注册流程 ✅ ``` #### 修改3:组件卸载时清理(清理时机3) **文件**:`src/pages/patient/Register.tsx` ```typescript // 清理时机3:组件卸载时清理表单 useEffect(() => { return () => { // 组件卸载时清理 dispatch(clearFormData()); form.resetFields(); console.log('注册页面已卸载,表单数据已清空'); }; }, [dispatch, form]); ``` **领域概念**: - **React生命周期**:useEffect cleanup函数 - **防御性编程**:即使用户没有完成注册就离开页面,也确保状态被清理 - **避免数据泄漏**:用户再次进入注册页面时,不会看到旧数据 --- ## 修复后的完整流程 ### 正常注册流程 ``` 用户进入注册页面 ↓ 填写患者信息 ↓ 每次字段变化 → dispatch(setFormData(allValues)) ↓ 选择体位 ↓ 点击体位 → dispatch(addSelectedView(view)) ↓ 点击"注册"按钮 ↓ handleRegister(): 1. 获取表单值:form.getFieldsValue() 2. 验证表单:schema.safeParse(values) 3. 验证通过 → 调用API:registerWork(registerInfo) ↓ API返回成功 ↓ 【三重清理】 1. dispatch(clearFormData()) → formSlice.formData = {} 2. form.resetFields() → Form内部状态清空 3. selectedViews自动清空 → viewSelection.selectedViews = [] ↓ 显示成功消息 ↓ 用户可以开始新的注册 ✅ ``` ### 用户离开页面流程 ``` 用户填写部分信息 ↓ 未完成注册,切换到其他页面 ↓ RegisterPage组件卸载 ↓ useEffect cleanup函数执行 ↓ 【清理】 1. dispatch(clearFormData()) 2. form.resetFields() ↓ 用户再次进入注册页面 ↓ 看到的是干净的空表单 ✅ ``` --- ## 状态管理架构图 ### 修复前(有Bug) ``` ┌─────────────────────────────────────────────┐ │ 注册成功后的状态 │ ├─────────────────────────────────────────────┤ │ Redux viewSelection.selectedViews: [] ✅ │ │ Redux formSlice.formData: {...旧数据} ❌ │ │ Ant Design Form State: {...旧数据} ❌ │ └─────────────────────────────────────────────┘ ↓ 状态不一致! ↓ 第二次注册时Bug ``` ### 修复后(正常) ``` ┌─────────────────────────────────────────────┐ │ 注册成功后的状态 │ ├─────────────────────────────────────────────┤ │ Redux viewSelection.selectedViews: [] ✅ │ │ Redux formSlice.formData: {} ✅ │ │ Ant Design Form State: {} ✅ │ └─────────────────────────────────────────────┘ ↓ 状态一致! ↓ 系统恢复初始状态 ``` --- ## 关键概念总结 ### 1. 双重状态管理 - **Form State**(组件级):负责UI交互 - **Redux State**(全局级):负责跨组件共享 ### 2. 状态同步 - 数据流入:`onValuesChange` → `dispatch(setFormData)` - 数据清理:必须同时清理两个状态 ### 3. 清理时机 - **时机1**:业务成功后(注册成功) - **时机3**:组件卸载时(防御性) ### 4. 因果关系 ``` 用户操作 → 状态变化 → 副作用处理 → UI更新 ``` --- ## 测试验证要点 ### 手动测试 1. **测试场景1**:连续注册两次 - 第一次:填写完整信息 → 注册成功 - 验证:表单已清空(UI和数据层都清空) - 第二次:不填表单 → 点击注册 - 预期:提示"必填项未填写" 2. **测试场景2**:填写后离开页面 - 填写部分信息 - 切换到其他页面 - 再次返回注册页面 - 预期:看到空表单 ### 验证点 - [ ] 注册成功后,`form.getFieldsValue()` 返回空对象 - [ ] 注册成功后,Redux `formSlice.formData` 为空对象 - [ ] 注册成功后,Redux `selectedViews` 为空数组 - [ ] 组件卸载后再进入,表单显示为空 - [ ] 控制台日志正确显示清理信息 --- ## 相关文件清单 ### 已修改文件 1. `src/states/patient/register/formSlice.ts` - 添加 `clearFormData` action 2. `src/pages/patient/Register.tsx` - 导入 `clearFormData`, `useEffect` - 实现清理时机1:注册成功后 - 实现清理时机3:组件卸载时 ### 相关参考文件 - `src/states/patient/viewSelection/index.ts`(selectedViews的清理) - `src/hooks/useRegisterState.ts`(状态获取) - `src/validation/patient/registerSchema.ts`(验证逻辑) --- ## 更新记录 | 日期 | 修改人 | 修改内容 | | --------- | ------ | --------------------------- | | 2025/10/8 | - | Bug分析、修复实现、文档创建 |