借助 Bloc,我们能够把 业务逻辑 从 视图层 完全分离,让组件既“聪明”又“干净”。本文从一段常见的
AppBar
代码出发,拆解 Bloc 的用法与最佳实践,并在最后总结常见陷阱与性能优化思路。
1 Bloc 简介
Bloc(Business Logic Component)模式:通过
Cubit / Bloc
(状态机)和State
(不可变数据),实现单向数据流。核心优势
- 可测试:业务逻辑与 UI 解耦,单元测试更容易。
- 可维护:状态不可变,调试时可追溯。
- 可扩展:跨模块共享同一个 Cubit/Bloc。
2 示例代码
AppBar(
title: BlocListener<MyCubit, MyState>(
listener: (context, state) {
// 仅在数据成功加载后,通知全局 notifier 更新用户信息
if (state is DataReady) {
context.read<AppNotifier>().refreshUserInfo();
}
},
child: BlocBuilder<MyCubit, MyState>(
// 根据不同状态,返回不同的 UI 片段
builder: (context, state) {
if (state is DataReady) {
return Text('未读: ${state.unreadCount}');
}
return const Text('加载中…');
},
),
),
actions: [...] // 省略无关代码
)
3 BlocListener vs BlocBuilder
作用 | 触发时机 | 使用场景示例 |
---|---|---|
BlocListener | 仅监听 状态变化 并执行 一次性 副作用(如导航、弹框、调用 Provider)。返回 Widget 必须为其 child ,性能消耗极低。 |
登录成功后跳转首页、请求完成后更新全局缓存 |
BlocBuilder | 每当 state 变化就 重新构建 UI;仅应构建屏幕上受影响的最小区域。 |
列表、计数器、加载进度条等可视化元素 |
结论:
- 副作用 ➔
BlocListener
- UI 渲染 ➔
BlocBuilder
二者可以叠加使用,如本示例所示。
4 为何放进 AppBar?
- 状态提升:
AppBar
常在根 Scaffold 中,适合展示全局信息(未读数、网络状态)。 - 即时反馈:
BlocBuilder
保证新状态第一时间同步到标题区,用户无需下拉刷新。 - 统一逻辑:同一个 Cubit 可被其他页面/组件复用,实现“多处展示、单点更新”。
5 与 Provider 协作
虽然项目已经使用 Bloc,但仍可以让 老代码或第三方依赖 保持 Provider/InheritedWidget:
if (state is DataReady) {
// Bloc → Provider 单向通知
context.read<AppNotifier>().refreshUserInfo();
}
- 单向依赖:Bloc 只负责发事件,不依赖 Provider;Provider 作为 UI 辅助层。
- 避免循环依赖:Provider 中不应再读取 Cubit,否则会形成环。
6 性能与陷阱
风险点 | 解决方案 |
---|---|
BlocBuilder 包裹范围过大,导致整棵 Widget 树重建 |
将 BlocBuilder 移到最小可重建单元(如一个 Text ) |
在 listener 里执行耗时逻辑 |
将耗时操作移到 Cubit 内或使用异步队列 |
同一 Cubit 在 dispose 前被多次创建 |
使用 BlocProvider.value 或 MultiBlocProvider 保证单例 |
7 最佳实践清单
- 状态命名清晰:
Loading → DataReady → Failure
,避免隐含意义。 - 保持 State 不可变:使用
equatable
保证对象比较精准。 - 分层组织:
Bloc / Cubit
写在domain
层,UI 只感知state
。 - 尽量合并小事件:批量更新数据,减少
emit
次数。
8 小结
通过一个简洁的 AppBar
示例,我们看到 Bloc 在 状态管理 与 副作用分离 方面的强大。只要遵循 单向数据流 与 最小重建 原则,Bloc 能让你的 Flutter 代码更加健壮、易测且高效。祝编码愉快!