@whynotsnow/dynamic-form
Version:
DynamicForm 是一个基于 React 和 Ant Design 的动态表单组件,支持复杂的表单联动、自定义处理器、状态管理和性能优化。
336 lines (248 loc) • 8.05 kB
Markdown
# 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 次减少到 1 次
2. **减少重渲染次数**: 从 N 次减少到 1 次
3. **提升响应速度**: 特别是在复杂表单中效果明显
## 使用方式
### 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. **数据一致性**: 通过值覆盖机制确保数据正确性
这种优化特别适合复杂表单和频繁字段更新的场景,能够显著改善用户体验。