Flutter Bloc状态更新问题:如何避免无关Widget重绘?
核心问题是你的状态设计采用了互斥的单一类型状态,每次更新都会覆盖另一种数据的状态依据,导致无关列表失去正确渲染的数据源。以下是两种可行的解决思路:
方案一:重构为包含全量数据的单一状态(推荐)
把互斥的IncomeSavedState和ExpenseSavedState合并成一个包含收入、支出全量数据的状态,比如FinanceDataLoadedState:
abstract class FinanceState {} class FinanceInitial extends FinanceState {} class FinanceDataLoadedState extends FinanceState { final List<Income> incomes; final List<Expense> expenses; FinanceDataLoadedState({required this.incomes, required this.expenses}); }
在Bloc处理保存事件时,根据操作类型更新对应列表,再发出包含完整数据的新状态:
class FinanceBloc extends Bloc<FinanceEvent, FinanceState> { FinanceBloc() : super(FinanceInitial()) { on<SaveIncomeEvent>((event, emit) { final currentState = state; List<Income> updatedIncomes = []; List<Expense> currentExpenses = []; if (currentState is FinanceDataLoadedState) { updatedIncomes = List.from(currentState.incomes)..add(event.income); currentExpenses = currentState.expenses; } else { updatedIncomes = [event.income]; } emit(FinanceDataLoadedState(incomes: updatedIncomes, expenses: currentExpenses)); }); on<SaveExpenseEvent>((event, emit) { final currentState = state; List<Expense> updatedExpenses = []; List<Income> currentIncomes = []; if (currentState is FinanceDataLoadedState) { updatedExpenses = List.from(currentState.expenses)..add(event.expense); currentIncomes = currentState.incomes; } else { updatedExpenses = [event.expense]; } emit(FinanceDataLoadedState(incomes: currentIncomes, expenses: updatedExpenses)); }); } }
之后两个列表的BlocBuilder统一判断FinanceDataLoadedState,直接渲染对应数据:
// 收入列表 BlocBuilder<FinanceBloc, FinanceState>( builder: (context, state) { if (state is FinanceDataLoadedState) { return ListView.builder( itemCount: state.incomes.length, itemBuilder: (context, index) => ListTile(title: Text(state.incomes[index].amount.toString())), ); } return const Text("暂无收入"); }, ) // 支出列表 BlocBuilder<FinanceBloc, FinanceState>( builder: (context, state) { if (state is FinanceDataLoadedState) { return ListView.builder( itemCount: state.expenses.length, itemBuilder: (context, index) => ListTile(title: Text(state.expenses[index].amount.toString())), ); } return const Text("暂无支出"); }, )
这种方式的好处是状态始终包含完整的财务数据,不管更新收入还是支出,两个列表都能保持正确渲染,后续制作折线图时也能直接从该状态获取支出数据,无需额外处理。
方案二:使用BlocBuilder的buildWhen过滤无关状态(临时过渡方案)
如果暂时不想重构状态,可以利用BlocBuilder的buildWhen参数,让每个列表只响应自己关心的状态变化,但需要确保每个状态都保留全量数据:
首先修改状态结构,让每个派生状态都包含收入和支出数据:
abstract class FinanceState {} class IncomeSavedState extends FinanceState { final List<Income> incomes; final List<Expense> expenses; IncomeSavedState({required this.incomes, required this.expenses}); } class ExpenseSavedState extends FinanceState { final List<Income> incomes; final List<Expense> expenses; ExpenseSavedState({required this.incomes, required this.expenses}); }
在Bloc处理事件时,更新对应列表并保留另一种数据:
on<SaveIncomeEvent>((event, emit) { final currentState = state; List<Income> updatedIncomes = []; List<Expense> currentExpenses = []; if (currentState is IncomeSavedState) { updatedIncomes = List.from(currentState.incomes)..add(event.income); currentExpenses = currentState.expenses; } else if (currentState is ExpenseSavedState) { updatedIncomes = List.from(currentState.incomes)..add(event.income); currentExpenses = currentState.expenses; } else { updatedIncomes = [event.income]; } emit(IncomeSavedState(incomes: updatedIncomes, expenses: currentExpenses)); });
最后给每个BlocBuilder添加buildWhen过滤条件:
// 收入列表 BlocBuilder<FinanceBloc, FinanceState>( buildWhen: (previous, current) => current is IncomeSavedState, builder: (context, state) { if (state is IncomeSavedState) { return ListView.builder( itemCount: state.incomes.length, itemBuilder: (context, index) => ListTile(title: Text(state.incomes[index].amount.toString())), ); } return const Text("暂无收入"); }, ) // 支出列表 BlocBuilder<FinanceBloc, FinanceState>( buildWhen: (previous, current) => current is ExpenseSavedState, builder: (context, state) { if (state is ExpenseSavedState) { return ListView.builder( itemCount: state.expenses.length, itemBuilder: (context, index) => ListTile(title: Text(state.expenses[index].amount.toString())), ); } return const Text("暂无支出"); }, )
这种方式需要维护两个状态的全量数据,代码冗余度高,长期来看不如方案一简洁。
关于拆分Bloc的顾虑
如果后续考虑拆分IncomeBloc和ExpenseBloc,也完全可以实现折线图功能:可以用StreamCombineLatest合并两个Bloc的状态流,或者创建一个FinanceOverviewBloc监听两个子Bloc的事件,整合数据后提供给折线图组件,不用担心数据无法统一的问题。
内容的提问来源于stack exchange,提问作者Tloz

