简介
Bloc (Business Logic Component) 是一个基于事件驱动的状态管理模式,它将业务逻辑从UI层分离出来,使用事件(Event)和状态(State)来管理应用数据流。
基本概念
Event
// 事件基类
abstract class CounterEvent {}
// 具体事件
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
State
// 状态基类
abstract class CounterState {
final int count;
const CounterState(this.count);
}
// 具体状态
class CounterInitial extends CounterState {
CounterInitial() : super(0);
}
class CounterUpdated extends CounterState {
CounterUpdated(int count) : super(count);
}
Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial()) {
on<IncrementEvent>((event, emit) {
emit(CounterUpdated(state.count + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterUpdated(state.count - 1));
});
}
}
基本用法
提供 Bloc
void main() {
runApp(
BlocProvider(
create: (context) => CounterBloc(),
child: MyApp(),
),
);
}
使用 Bloc
// 监听状态
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
);
// 发送事件
context.read<CounterBloc>().add(IncrementEvent());
完整示例
// 用户相关事件
abstract class UserEvent {}
class LoadUserEvent extends UserEvent {}
class UpdateUserEvent extends UserEvent {
final String name;
final int age;
UpdateUserEvent({required this.name, required this.age});
}
// 用户相关状态
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final String name;
final int age;
UserLoaded({required this.name, required this.age});
}
class UserError extends UserState {
final String message;
UserError(this.message);
}
// 用户 Bloc
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository repository;
UserBloc({required this.repository}) : super(UserInitial()) {
// 加载用户
on<LoadUserEvent>((event, emit) async {
emit(UserLoading());
try {
final user = await repository.getUser();
emit(UserLoaded(name: user.name, age: user.age));
} catch (e) {
emit(UserError('Failed to load user: $e'));
}
});
// 更新用户
on<UpdateUserEvent>((event, emit) async {
emit(UserLoading());
try {
await repository.updateUser(event.name, event.age);
emit(UserLoaded(name: event.name, age: event.age));
} catch (e) {
emit(UserError('Failed to update user: $e'));
}
});
}
}
// 页面组件
class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Demo')),
body: BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading) {
return Center(child: CircularProgressIndicator());
}
if (state is UserError) {
return Center(child: Text(state.message));
}
if (state is UserLoaded) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Name: ${state.name}',
style: TextStyle(fontSize: 24),
),
Text(
'Age: ${state.age}',
style: TextStyle(fontSize: 24),
),
ElevatedButton(
onPressed: () {
context.read<UserBloc>().add(
UpdateUserEvent(
name: 'John Doe',
age: 30,
),
);
},
child: Text('Update User'),
),
],
),
);
}
return Center(
child: ElevatedButton(
onPressed: () {
context.read<UserBloc>().add(LoadUserEvent());
},
child: Text('Load User'),
),
);
},
),
);
}
}
高级用法
BlocListener
BlocListener<UserBloc, UserState>(
listener: (context, state) {
if (state is UserError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
child: YourWidget(),
)
BlocConsumer
BlocConsumer<UserBloc, UserState>(
listener: (context, state) {
// 处理副作用
},
builder: (context, state) {
// 构建UI
return YourWidget();
},
)
状态流转换
Stream<State> transformEvents<Event, State>(
Stream<Event> events,
Stream<State> Function(Event event) mapper,
) {
return events
.debounceTime(Duration(milliseconds: 300))
.switchMap(mapper);
}
最佳实践
- 用 sealed class 定义状态,靠 switch 穷举防漏分支:Dart 3 的
sealed class LoginState配 switch,漏写一个分支编译直接报错——这比”记得处理所有状态”靠谱得多 - 一个 Bloc 只管一个业务域:登录 Bloc 里别塞注册逻辑;事件名带前缀(
LoginSubmitted而不是裸Submitted),多个 Bloc 共处时事件不会串味 - 跳转/弹 Toast 用 BlocListener,别写在 BlocBuilder 的 builder 里:builder 会被重复调用,导航代码会执行多次,页面会被 push 好几遍
- Bloc 之间别直接调用:A 需要 B 的状态时,用 BlocListener 监听 B 再
add事件给 A,保持单向数据流——在 Bloc 里context.read<OtherBloc>()然后调它的方法,是埋雷
注意事项
- 网络请求可以放 Bloc 里,但 http client 要从构造器注入:
LoginBloc({required this.repo}),测试时才能 mock 掉网络层;在 Bloc 里直接Dio()new 出来,单测就没法写了 - 事件是”发生了什么”(过去式:
LoginSubmitted),状态是”现在是什么”(LoginSuccess);“正在加载”是状态不是事件,别为了 loading 多发一个事件 - 大对象别塞进 state:图片 bytes、几百条的列表放进 state,每次 emit 都要参与
==比较(Equatable),直接拖慢;传 id,数据放 repository 里缓存 - 别手动 close 被多个页面共享的 Bloc:BlocProvider 会自动调 close,手动 close 之后其他页面再
add事件直接抛异常
实战案例
Bloc 的核心思想(单向数据流)我认同,但实践中我选了 GetX,原因很俗:快。以 某物流冷链项目 的司机认证页为例,用 GetX 就是 controller 里几个 .obs 变量 + Obx 包 UI,半天写完;用 Bloc 要定义 VerifyEvent、VerifyState、VerifyBloc,还要处理表单校验、图片上传、提交三个状态机,代码量翻倍。对于”表单+列表”为主的业务 App,Bloc 的 ceremony(仪式感代码)是负担。但我也承认:如果做的是 IM 聊天、协同编辑这种状态机复杂的场景,Bloc 的显式状态是值得的。简单场景别上重武器,复杂场景别偷懒——这是我对状态管理的全部看法。
总结
Bloc 提供了一种清晰的方式来管理应用状态,通过分离业务逻辑和UI表现层,可以构建出更易维护、测试的应用程序。理解并掌握 Bloc 的使用对于开发高质量的 Flutter 应用至关重要。
