Flutter Bloc模式下自定义类/基础类型作为State的Widget选择疑问
嘿,我来帮你把这个问题掰扯清楚~ 其实核心原因和Bloc判断状态变化的逻辑有关,咱们一步步拆解:
为什么用int作为State时StatelessWidget能正常工作?
int是Dart里的不可变类型,每次你执行yield currentState + 1或者yield currentState -1时,都会生成一个全新的int对象。Bloc内部会对比新旧状态的引用(或者说是否是同一个对象),发现状态确实发生了变化,就会通知BlocBuilder重建UI——所以哪怕是StatelessWidget,也能正常响应状态更新。
为什么自定义CounterState时最初必须用StatefulWidget?
你最初的代码里犯了一个Bloc开发的常见错误:修改了同一个CounterState实例的属性,然后yield这个旧实例。
看你最初的CounterBloc代码:
CounterState counterState = new CounterState(counter: 0); @override Stream<CounterState> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.Decrement: counterState.decrementCounter(); // 修改同一个实例的属性 yield counterState; // 还是yield原来的对象引用 break; // ... } }
Bloc对比新旧状态时,发现前后是同一个对象(引用完全相等),就会认为“状态没有变化”,因此不会触发BlocBuilder的重建。这时候你用StatefulWidget能更新,大概率是你在StatefulWidget里手动调用了setState,或者其他地方触发了组件重建,但这并不是Bloc的正常工作流程。
解决方法:每次状态变化时生成新的CounterState实例
就像你后来根据提示修改的那样,每次yield一个全新的CounterState对象,Bloc就能检测到状态变化,不管是Stateless还是StatefulWidget,BlocBuilder都会正常触发UI更新。
更规范的做法:把CounterState设计成不可变类
为了避免不小心修改旧状态,推荐把自定义State设计成不可变类(用final字段),并提供copyWith方法来生成新实例,这样代码更简洁也更符合Bloc的最佳实践:
// 不可变的CounterState class CounterState { final int counter; const CounterState({required this.counter}); // 生成新状态的copyWith方法 CounterState copyWith({int? counter}) { return CounterState( counter: counter ?? this.counter, ); } }
对应的Bloc代码可以简化成:
class CounterBloc extends Bloc<CounterEvent, CounterState> { @override CounterState get initialState => const CounterState(counter: 0); @override Stream<CounterState> mapEventToState(CounterEvent event) async* { switch (event) { case CounterEvent.Decrement: yield currentState.copyWith(counter: currentState.counter - 1); break; case CounterEvent.Increment: yield currentState.copyWith(counter: currentState.counter + 1); break; } } }
这样不管是用Stateless还是StatefulWidget,BlocBuilder都能精准响应状态变化,UI更新完全由Bloc的状态驱动,不需要额外手动触发。
内容的提问来源于stack exchange,提问作者Hunt

