Flutter Bloc:修改State内列表后BlocBuilder未触发调用
问题诱因
Bloc 触发组件重建的前提是emit的新状态和旧状态不相等,未触发重建的核心原因是错误使用可变状态做原地修改,具体问题点如下:
- 直接在原有state实例上修改属性:不管是调用
state.selectedFilters!.remove()删除列表元素、给state.selectedAvailability重新赋值,还是修改medicineList内元素的checked属性,都是在原state对象的内存空间上做修改,没有生成新的状态对象。 - emit时传入的还是原state对象的引用:Bloc收到emit的状态时会对比新旧状态,如果是同一个对象实例(或者SearchState使用了Equatable做值比较,且所有属性的引用都没有变化),就会判定状态没有更新,不会通知BlocBuilder重建。
- 列表修改逻辑存在无效代码:Dart中
Iterable.map()是惰性执行的,原代码既没有消费map的返回结果,也没有将修改后的内容重新赋值给state,这段修改药品选中状态的代码本身就不会生效。
修复方案
首先要把SearchState改成不可变类,所有属性都用final修饰,禁止直接修改原state的任何属性,每次更新状态都要创建全新的state实例传入emit。
修正后的事件处理代码参考:
FutureOr<void> _removeFilter(OnFilterRemoved event, Emitter<SearchState> emit) { // 从旧状态拷贝需要修改的属性,生成新的对象/集合 final newSelectedFilters = List<String>.from(state.selectedFilters ?? []); var newAvailability = state.selectedAvailability; var newMedicineList = state.medicineList; if (newSelectedFilters.isNotEmpty) { newSelectedFilters.remove(event.text); if (event.text == newAvailability) { newAvailability = ''; } else { // 生成新的药品列表,不修改原列表内的对象 newMedicineList = state.medicineList.map((element) { if (element.name == event.text) { // 有copyWith方法就用copyWith,没有就手动调用构造函数创建新实例,不要直接修改原element属性 return element.copyWith(checked: false); } return element; }).toList(); // 必须调用toList()触发map逻辑执行,生成新列表 } } // 传入全新的state实例,禁止直接传原state emit(SearchState( selectedFilters: newSelectedFilters, selectedAvailability: newAvailability, medicineList: newMedicineList, // 其余state属性从旧state原样拷贝,不要遗漏 )); }
额外注意事项:
- 如果
SearchState使用了Equatable,必须把所有属性都加入props列表,否则值对比时会出现判断错误。 - 列表、自定义类类型的属性更新时,一定要生成新的实例,不要直接修改原实例的属性,否则就算创建了新的State对象,属性引用还是指向旧对象,Equatable还是会判定两个状态相等。
- 不需要额外配置BlocBuilder的buildWhen参数,只要状态相等判断逻辑正确,修改后就会正常触发重建。
内容的提问来源于stack exchange,提问作者Taha alam
相关产品推荐
相关产品推荐

