在表单状态管理中避免BlocBuilder不必要的重建
表单状态更新导致所有BlocBuilder不必要重建的问题
我在Flutter项目中使用Bloc管理表单状态,表单包含TextFormField、下拉选择器等多种输入组件,每个组件都配置了onChange事件并绑定到Bloc事件,所有组件都用BlocBuilder包裹。状态类FormStat通过formData这个Map存储用户的所有输入内容。
目前的问题是:每当formData更新并触发状态发射时,所有订阅该状态的BlocBuilder都会触发重建。我尝试使用buildWhen来过滤不必要的重建,但没有达到预期效果。相关代码如下:
class BuildFormFields { static Widget buildFormField(FormItem field, BuildContext context) { var temp = '''{ "data_1": "option 1", "data_2": "option 2", "data_3": "option 3" }'''; Map<String, dynamic> payload = jsonDecode(temp); Map<String, Future<Map<String, dynamic>>> dropdownOptions = {}; dropdownOptions['dropdownOptions'] = Future.value(payload); final formBloc = context.read<FormBloc>(); switch (field.type) { case "textfield": formBloc .add(FormEventSetTextFormController(controller: field.variable!)); return BlocBuilder<FormBloc, FormStat>(buildWhen: (previous, current) { log("pre ${previous.formData[field.variable]} ,${field.variable}"); log("curr ${current.formData[field.variable]},${field.variable}"); return previous.controllers[field.variable]?.text == current.controllers[field.variable]?.text; }, builder: (context, state) { log("REBUILD Happened textfield ${field.variable}, ${state.formData}"); return CustomTextFormField( initialValue: state.controllers[field.variable]?.text, isRequired: field.isRequired ?? true, fieldName: field.enLabel!, controller: state.controllers[field.variable], onSaved: (value) { formBloc.add(FormEventOnSavedClickOrOnChanged( variable: field.variable!.trim(), value: value)); }, onChanged: (value) { formBloc.add(FormEventOnSavedClickOrOnChanged( variable: field.variable!.trim(), value: value)); }, keyboardType: TextInputType.text, ); }); case "dropdown": log("dropdown"); return BlocBuilder<FormBloc, FormStat>(builder: (context, state) { return CustomDropdown( formData: state.formData, field: field, parentNotifier: (variable) { // _parentNotifier(variable); }, onChanged: (value) { log("dropdown_onchanged_value ${value}"); formBloc.add(FormEventOnSavedClickOrOnChanged( variable: field.variable!.trim(), value: value)); }, dropdownOptions: dropdownOptions, ); }); case "textview": formBloc .add(FormEventSetTextFormController(controller: field.variable!)); formBloc.add(FormEventRegisterDependencies( dependencies: field.dependencies!, variableName: field.variable)); return BlocBuilder<FormBloc, FormStat>(builder: (context, state) { bool dependenciesMet = true; if (dependenciesMet) { formBloc.add(FormEventApiCalls(endpoint: field.endpoint)); return CustomTextFormField( isRequired: field.isRequired ?? true, fieldName: field.enLabel!, readOnly: true, controller: state.controllers[field.variable], keyboardType: TextInputType.text, ); } return Container(); }); case "single_checkbox_bool": return BlocBuilder<FormBloc, FormStat>(builder: (context, state) { return Text("single_checkbox_bool"); }); default: return Text("Unknown widget Type"); } } } @freezed class FormStat with _$FormStat { const factory FormStat( {AppForm? baseFormUiModel, dynamic stepFormUiModel, @Default({}) Map<String, dynamic> formData, @Default({}) Map<String, TextEditingController> controllers, @Default({}) Map<String, Set<String>> registrar}) = _FormStat; } FutureOr<void> onFormEventOnSavedClickOrOnChanged( FormEventOnSavedClickOrOnChanged event, Emitter<FormStat> emit, ) async { final updatedFormData = Map<String, dynamic>.from(state.formData); final updatedContoller = Map<String, TextEditingController>.from(state.controllers); updatedFormData[event.variable] = event.value; updatedContoller[event.variable]?.text = event.value!; emit(state.copyWith( controllers: updatedContoller, formData: updatedFormData)); log("Updated Form Data: ${state.formData}"); }
问题修复方案
1. 修正textfield的buildWhen逻辑
你当前的buildWhen条件写反了,它表示“前后状态的控制器文本相同时才重建”,但我们需要的是当前字段内容变化时才重建,所以修改为:
return BlocBuilder<FormBloc, FormStat>( buildWhen: (previous, current) { return previous.controllers[field.variable]?.text != current.controllers[field.variable]?.text; }, builder: (context, state) { // 原有代码 }, );
2. 给其他组件添加buildWhen过滤
除了textfield,其他组件的BlocBuilder都没有过滤条件,导致任何状态更新都会触发重建:
- Dropdown组件:只在当前字段的
formData值变化时重建
return BlocBuilder<FormBloc, FormStat>( buildWhen: (previous, current) { return previous.formData[field.variable] != current.formData[field.variable]; }, builder: (context, state) { // 原有Dropdown代码 }, );
- TextView组件:只在当前字段的控制器文本变化时重建
return BlocBuilder<FormBloc, FormStat>( buildWhen: (previous, current) { return previous.controllers[field.variable]?.text != current.controllers[field.variable]?.text; }, builder: (context, state) { // 原有TextView代码 }, );
3. 移除builder中的重复事件触发
在textview的BlocBuilder中,每次重建都会调用formBloc.add(FormEventApiCalls(...)),这会引发不必要的事件循环和重建。应该将事件移到组件初始化阶段:
将textview组件封装为StatefulWidget,在initState中执行一次性操作:
class TextViewFormField extends StatefulWidget { final FormItem field; const TextViewFormField({super.key, required this.field}); @override State<TextViewFormField> createState() => _TextViewFormFieldState(); } class _TextViewFormFieldState extends State<TextViewFormField> { @override void initState() { super.initState(); final formBloc = context.read<FormBloc>(); formBloc.add(FormEventSetTextFormController(controller: widget.field.variable!)); formBloc.add(FormEventRegisterDependencies( dependencies: widget.field.dependencies!, variableName: widget.field.variable )); // 仅初始化时触发一次API请求 formBloc.add(FormEventApiCalls(endpoint: widget.field.endpoint)); } @override Widget build(BuildContext context) { return BlocBuilder<FormBloc, FormStat>( buildWhen: (previous, current) { return previous.controllers[widget.field.variable]?.text != current.controllers[widget.field.variable]?.text; }, builder: (context, state) { bool dependenciesMet = true; if (dependenciesMet) { return CustomTextFormField( isRequired: widget.field.isRequired ?? true, fieldName: widget.field.enLabel!, readOnly: true, controller: state.controllers[widget.field.variable], keyboardType: TextInputType.text, ); } return Container(); }, ); } }
4. 优化状态更新逻辑
在onFormEventOnSavedClickOrOnChanged中,无需复制整个controllersMap,因为TextEditingController是可变对象,直接修改其文本即可,仅更新formData并发射状态:
FutureOr<void> onFormEventOnSavedClickOrOnChanged( FormEventOnSavedClickOrOnChanged event, Emitter<FormStat> emit, ) async { final updatedFormData = Map<String, dynamic>.from(state.formData); updatedFormData[event.variable] = event.value; // 直接修改目标控制器文本,无需复制整个Map state.controllers[event.variable]?.text = event.value!; emit(state.copyWith(formData: updatedFormData)); log("Updated Form Data: ${updatedFormData}"); }
内容的提问来源于stack exchange,提问作者Pe be
相关产品推荐
相关产品推荐

