You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:43:10