UNPKG

@whynotsnow/dynamic-form

Version:

DynamicForm 是一个基于 React 和 Ant Design 的动态表单组件,支持复杂的表单联动、自定义处理器、状态管理和性能优化。

336 lines (248 loc) 8.05 kB
# DynamicForm 批量更新机制 ## 问题背景 在原始的 DynamicForm 实现中,每次字段值变化都会触发单独的 `dispatch` 调用,这导致: 1. **多次状态更新**: 每个字段变化都会触发一次 reducer 执行 2. **频繁重渲染**: 每次状态更新都会导致组件重新渲染 3. **性能问题**: 在复杂表单中,多个字段同时变化时性能下降明显 ## 优化方案 ### 1. 批量更新机制 通过收集多个字段的更新,然后一次性执行 `dispatch`,减少状态更新次数。 ```typescript // 优化前:多次 dispatch Object.entries(changedValues).forEach(([fieldId, value]) => { dispatch({ type: 'SET_FIELD_VALUE', payload: { fieldId, value } }); }); // 优化后:批量 dispatch const updates = {}; Object.entries(changedValues).forEach(([fieldId, value]) => { updates[fieldId] = value; }); dispatch({ type: 'SET_FIELD_VALUES', payload: { values: updates } }); ``` ### 2. 实现细节 #### useStateSync 批量更新 ```typescript export function useStateSync({ form, state, dispatch }: UseFormSyncProps) { const pendingUpdatesRef = useRef<Record<string, any>>({}); // 批量更新函数 const batchDispatch = useCallback(() => { const updates = pendingUpdatesRef.current; if (Object.keys(updates).length === 0) return; dispatch({ type: 'SET_FIELD_VALUES', payload: { values: updates } }); // 清空待更新队列 pendingUpdatesRef.current = {}; }, [dispatch]); // 添加更新到队列 const addToUpdateQueue = useCallback((fieldId: string, value: any) => { pendingUpdatesRef.current[fieldId] = value; }, []); // Form → State 同步时使用批量更新 const syncFormStateToStore = (changedValues, allValues) => { // 清空之前的待更新队列 pendingUpdatesRef.current = {}; // 收集所有需要更新的字段 Object.entries(changedValues).forEach(([fieldId, value]) => { const currentStateValue = state.fieldValues[fieldId]; if (currentStateValue !== value) { addToUpdateQueue(fieldId, value); } }); // 执行批量更新 batchDispatch(); }; return { syncFormStateToStore, batchDispatch, addToUpdateQueue }; } ``` #### EffectResultHandler 批量更新 ```typescript export function handleEffectResult(result, context) { let hasValueUpdates = false; Object.entries(result).forEach(([key, value]) => { for (const handler of handlers.values()) { if (handler.canHandle(key, value)) { if (handler.name === 'value' && context.addToUpdateQueue) { // 使用批量更新 context.addToUpdateQueue(context.fieldName, value); hasValueUpdates = true; } else { // 其他类型仍然使用单个更新 handler.handle(context, value); } break; } } }); // 如果有 value 类型的更新,立即执行批量 dispatch if (hasValueUpdates && context.batchDispatch) { context.batchDispatch(); } } ``` ### 3. Reducer 支持 确保 reducer 支持批量更新: ```typescript case 'SET_FIELD_VALUES': { const { values } = action.payload; return { ...state, fieldValues: { ...state.fieldValues, ...values } }; } ``` ## 重复调用问题与解决方案 ### 问题分析 在批量更新机制中,存在两个地方调用 `addToUpdateQueue`: 1. **useStateSync**: 用户输入时调用 2. **EffectResultHandler**: Effect 执行时调用 这可能导致以下问题: - 重复的字段更新 - 时序问题 - 数据不一致 ### 解决方案 #### 1. 分离更新队列 ```typescript // useStateSync 中的队列(用户输入) const userInputQueue = useRef<Record<string, any>>({}); // EffectResultHandler 中的队列(Effect 更新) const effectUpdateQueue = useRef<Record<string, any>>({}); ``` #### 2. 立即执行策略 ```typescript // useStateSync: 用户输入后立即执行 syncFormStateToStore(changedValues, allValues) { // 收集用户输入 Object.entries(changedValues).forEach(([fieldId, value]) => { addToUpdateQueue(fieldId, value); }); // 立即执行批量更新 batchDispatch(); } // EffectResultHandler: Effect 执行后立即执行 handleEffectResult(result, context) { // 收集 Effect 更新 if (handler.name === 'value') { context.addToUpdateQueue(context.fieldName, value); } // 立即执行批量更新 context.batchDispatch(); } ``` #### 3. 值覆盖机制 由于使用同一个 `pendingUpdatesRef`,后添加的值会覆盖先添加的值: ```typescript // 用户输入: employeeCount = 300 addToUpdateQueue('employeeCount', 300); // Effect 执行: employeeCount = 600 addToUpdateQueue('employeeCount', 600); // 最终结果: employeeCount = 600 (Effect 的值覆盖了用户输入) ``` 这种机制确保了: - 用户输入和 Effect 更新不会冲突 - 最终值是正确的(通常是 Effect 的结果) - 避免了数据不一致的问题 ## 性能对比 ### 测试场景 - **单个更新**: 5个字段分别更新 - **批量更新**: 5个字段一次性更新 ### 性能指标 ```typescript // 性能测试结果示例 [Performance] 性能对比: 单个更新: 15.23ms 批量更新: 3.45ms 性能提升: 77.35% ``` ### 实际效果 1. **减少状态更新次数**: 从 N 次减少到 12. **减少重渲染次数**: 从 N 次减少到 13. **提升响应速度**: 特别是在复杂表单中效果明显 ## 使用方式 ### 1. 自动批量更新 批量更新机制是自动启用的,无需额外配置: ```typescript // 用户输入多个字段时,会自动批量更新 const syncFormStateToStore = (changedValues, allValues) => { // 自动收集所有变化并批量更新 Object.entries(changedValues).forEach(([fieldId, value]) => { addToUpdateQueue(fieldId, value); }); batchDispatch(); }; ``` ### 2. Effect 批量更新 Effect 执行结果也会使用批量更新: ```typescript // Effect 返回多个字段更新时 { value: newValue, visible: true, disabled: false } // 会自动批量更新 value 字段,其他字段使用单个更新 ``` ## 兼容性 ### 1. 向后兼容 - 保持原有的单个更新 API - 自动检测并使用批量更新机制 - 不影响现有功能 ### 2. 渐进式优化 - 可以逐步启用批量更新 - 支持混合使用单个和批量更新 - 提供性能监控工具 ## 监控和调试 ### 1. 性能监控 ```typescript import { usePerformanceMonitor } from './performanceTest'; const { startTimer, endTimer } = usePerformanceMonitor(); // 监控更新性能 startTimer('batch-update'); // 执行批量更新 endTimer('batch-update'); ``` ### 2. 调试日志 ```typescript // 查看批量更新日志 console.log('[useStateSync] 执行批量更新:', updates); console.log('[effectResultHandler] 使用批量更新机制'); ``` ### 3. 测试工具 ```typescript import { testBatchUpdateLogic, testDuplicateCalls } from './testBatchUpdate'; // 测试批量更新逻辑 testBatchUpdateLogic(); // 测试重复调用问题 testDuplicateCalls(); ``` ## 最佳实践 ### 1. 合理使用批量更新 - 对于相关的字段更新,使用批量更新 - 对于独立的字段更新,可以使用单个更新 - 根据实际性能需求选择更新策略 ### 2. 性能优化 - 启用渲染优化 - 合理设计字段依赖关系 - 避免不必要的 Effect 触发 ### 3. 监控和维护 - 定期检查性能指标 - 监控批量更新的效果 - 根据实际使用情况调整策略 ## 总结 批量更新机制通过减少 `dispatch` 调用次数,显著提升了 DynamicForm 的性能: 1. **性能提升**: 减少状态更新和重渲染次数 2. **用户体验**: 提升表单响应速度 3. **可维护性**: 保持代码简洁和向后兼容 4. **可扩展性**: 支持更复杂的表单场景 5. **数据一致性**: 通过值覆盖机制确保数据正确性 这种优化特别适合复杂表单和频繁字段更新的场景,能够显著改善用户体验。