Flutter Bloc同一Bloc内如何跨事件复用结果避免重复调用API
实现方案
核心原则是以Bloc当前持有的状态作为唯一可信数据源,不需要额外维护独立的缓存变量,直接从已发射的列表状态中取已有数据拼接即可,从根源上避免缓存和状态不一致的问题,符合Bloc状态管理的最佳实践。
具体实现逻辑
- 处理
ExpenseAdd事件时,首先判断当前Bloc的生效状态:如果状态为ExpenseFetched,说明列表已经加载完成,直接从状态中取出已有的列表数据 - 拼接新增的开销条目时,必须生成新的列表对象,不要直接修改原状态持有的列表引用(Dart中List为引用类型,直接修改原列表无法触发Bloc的状态更新,也违反状态不可变原则)
- 拼接完成后直接发射新的
ExpenseFetched状态即可,不需要重复调用接口 - 增加兜底逻辑:如果当前状态不是
ExpenseFetched(比如还在加载中、未初始化),可以根据业务需求选择触发列表拉取、提示加载中等处理
修改后的完整代码
ExpenseBloc() : super(ExpenseInitial()) { on<ExpenseEvent>((ExpenseEvent event, Emitter<ExpenseState> emit) async { if (event is ExpenseFetchList) { emit(ExpenseFetching()); List<Expense> e = dummyExpenseList; //await event.useCase.getAll(); // TODO: remove Future.delayed await Future.delayed(const Duration(seconds: 5), () { emit(ExpenseFetched(list: e)); }); return; } if (event is ExpenseAdd) { // 从当前状态获取已加载的列表,无冗余缓存 if (state is ExpenseFetched) { final currentState = state as ExpenseFetched; // 生成新列表,不修改原状态的引用 final updatedList = List<Expense>.from(currentState.list) ..add(event.expense); // ExpenseAdd事件需通过expense字段传递新增条目 emit(ExpenseFetched(list: updatedList)); } else { // 兜底逻辑:列表未加载完成时的处理,可根据业务调整 emit(ExpenseFetching()); add(ExpenseFetchList()); } return; } }); }
注意事项
- 不要在Bloc中单独定义私有List字段存储列表数据做缓存:这种做法会导致状态和缓存分离,后续如果有删除、编辑、分页加载等操作,很容易漏更新缓存导致数据不一致
- 所有对列表的修改操作(新增、删除、编辑)都要遵循「从当前状态取旧数据→生成新的列表副本→修改副本→发射新状态」的流程,严格保证状态不可变
- 如果后续使用freezed等工具生成密封类状态,可以用
state.maybeWhen/state.map的写法替代手动类型判断,代码会更简洁安全
内容的提问来源于stack exchange,提问作者Zain Arshad
相关产品推荐
相关产品推荐

