本文首发于公众号:移动开发那些事:Flutter开发者必备:状态管理Bloc的实用详解
1 引言
在 Flutter
应用开发中,状态管理是一个核心挑战。随着应用复杂度的提升,如何高效地管理状态、处理依赖注入以及实现路由导航,成为了开发者必须面对的问题。本文将探讨BLoc
框架的特点、基础使用以及高级应用场景,帮助开发者全面掌握这一框架。
2 BLoc介绍
BLoC(Business Logic Component)
是一种由 Google
推出的状态管理模式,最初为 Angular
框架设计,后被广泛应用于 Flutter
开发中。其核心思想是将业务逻辑与 UI
界面分离,通过流(Stream
)实现单向数据流,使得状态变化可预测且易于测试。其中最关键的概念:
- Event :用户交互或系统触发动作(如按钮点击、网络请求),以类或枚举形式定义;
- State: 应用(业务)所处的状态(如加载中,加载错误,空数据等),每个状态对应于不同的
UI
展示逻辑 - Bloc(逻辑组件): 接收业务的事件流,通过 mapEventToState或
on<Event>
处理逻辑,输出新状态流;
它的数据流一般为:UI
-> add(Event) (动作触发或某些条件触发)
-> Bloc(处理对应事件,并转换出新的状态)
-> emit(State) (提交新的状态机制)
-> UI(接收到新状态,并重新沉浸)
并且这个框架的核心目标为:
- 状态变更可预测:通过严格的 "事件 - 状态" 映射,确保状态变化可追溯、可复现;
- 业务逻辑可测试:业务逻辑与 UI 解耦后,可通过单元测试验证,无需依赖 UI 环境;
- 代码可维护性:分层设计使代码结构清晰,降低复杂度;
- 响应式更新:基于数据流实现 UI 与状态的自动同步。
3 BLoc使用
下面以一个简单的计数器应用示例,展示 BLoC 模式的基本用法:
3.1 不使用框架实现BLoc
BLoC
模式的核心是基于Dart
的 Stream
和 StreamController
实现的:
Stream
:用于异步数据序列的核心抽象StreamController
: 是Stream
的控制器,可用于添加数据和监听事件;Sink
:用于向Stream
添加数据的入口;
这个模式的处理过程为:
UI
通过Sink
将事件发送到BLoC
BLoC
接收事件并处理业务逻辑BLoC
将新状态添加到状态流UI
通过StreamBuilder
监听状态变化并更新
scala
import 'dart:async';
// 步骤1: 定义事件类
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// 步骤2 . 定义状态类
class CounterState {
final int count;
CounterState(this.count);
}
// 步骤3. 实现 BLoC 逻辑
class CounterBloc {
// 状态流
final _stateController = StreamController<CounterState>();
Stream<CounterState> get stateStream => _stateController.stream;
// 事件流
final _eventController = StreamController<CounterEvent>();
Sink<CounterEvent> get eventSink => _eventController.sink;
// 当前状态
CounterState _state = CounterState(0);
CounterBloc() {
// 监听事件流并处理事件
_eventController.stream.listen(_mapEventToState);
}
// 处理事件,并将新状态添加到状态流中
void _mapEventToState(CounterEvent event) {
if (event is IncrementEvent) {
_state = CounterState(_state.count + 1);
} else if (event is DecrementEvent) {
_state = CounterState(_state.count - 1);
}
// 将新状态添加到状态流
_stateController.add(_state);
}
void dispose() {
_stateController.close();
_eventController.close();
}
}
// 步骤4 . 在 Flutter 中使用 BLoC
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('BLoC Counter')),
body: CounterPage(),
),
);
}
}
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
final CounterBloc _counterBloc = CounterBloc();
@override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 使用 StreamBuilder 监听状态的变化
StreamBuilder<CounterState>(
stream: _counterBloc.stateStream,
initialData: _counterBloc._state,
builder: (context, snapshot) {
// 这里可以根据不同的状态,返回不同的UI
return Text(
'Count: ${snapshot.data!.count}',
style: TextStyle(fontSize: 24),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('+'),
// 用户动作,触发事件生成,通过eventSink 将事件发送到BLoc
onPressed: () => _counterBloc.eventSink.add(IncrementEvent()),
),
ElevatedButton(
child: Text('-'),
// 用户动作,触发事件生成
onPressed: () => _counterBloc.eventSink.add(DecrementEvent()),
),
],
),
],
),
);
}
}
3.2 使用框架实现BLoc
在实际项目中,通常会使用官方的bloc
库或 flutter_bloc
库来简化 BLoC
的实现, 使用前需要先在工程的pubspec.yaml
文件中增加第三方库的依赖
yaml
dependencies:
flutter_bloc: ^9.1.1
使用flutter _bloc
库实现同样的计数器的功能
scala
// 使用 flutter_bloc 库实现相同功能
import 'package:flutter_bloc/flutter_bloc.dart';
// 步骤1:定义事件类
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// 步骤2:实现BLoC 类,这里通过继承Bloc来实现
// 这里没有直接定义CounterState类了,而是直接通过int来替代(实际使用中需要根据复杂程度来决定是否要状态类)
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
// 将事件转换为对应的状态
if (event is Increment) {
// 通过yield发送新事件
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}
// 步骤3:UI, 使用 BlocBuilder 简化 UI
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 BlocBuilder 简化 UI
return BlocBuilder<CounterBloc, int>(
builder: (context, count) {
// 这里可以根据不同的状态,返回不同的UI
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $count', style: TextStyle(fontSize: 24)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('+'),
// 触发新事件的生成
onPressed: () => context.read<CounterBloc>().add(Increment()),
),
ElevatedButton(
child: Text('-'),
onPressed: () => context.read<CounterBloc>().add(Decrement()),
),
],
),
],
);
},
);
}
}
这里只是实现了一个很简单的计数器的示例,实际的使用过程中需要业务和对应状态的复杂程度,灵活使用BlocListener
,BlocBuilder
,BlocProvider
这几个组件来构建业务框架;
4 flutter_bloc 库
Flutter
中,其实有两个库都能实现BLoc
的模式:flutter_bloc
和cubit
,:
- cubit:
BLoc
模式的简化版,省略了事件类,直接通过emit
去发射状态,相对轻量,比较适合简单状态管理或中小型的项目; - flutter_bloc :
BLoc
模式的完整版,支持各种事件的监听,状态的转换,适配于复杂业务逻辑和大型项目; 有需要的可自行去了解cubit
库的实现与使用,这里就只介绍flutter_bloc
库。
4.1 核心类
-
Bloc:管理状态的核心类,处理事件并转换为状态;
-
Event:触发状态变化的抽象,通常为类;
-
State:应用状态的抽象,通常为类
-
BlocBuilder :响应状态变化的
Flutter
组件 -
BlocProvider :依赖注入工具(继承自
InheritedWidget
),使Bloc
可在widget
树中访问; -
BlocListener
: 响应状态变化,适合处理一次性操作,如导航,弹窗等; -
BlocSelector
: 选择性重建 UI,避免不必要的刷新 -
BlocConsumer
: 合并BlocBuilder
和BlocListener
4.1.1 BlocBuilder
其的主要作用为:
- 监听 Bloc 状态变化;
- 状态变化时自动重建 widget
- 接收
buildWhen
参数,用于控制是否重建
kotlin
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('当前计数:${state.count}'); // 根据状态渲染UI
},
);
4.1.2 BlocProvider
其的主要作用为:
- 支持多层嵌套,形成 Bloc 层级结构;
- 通过
BlocProvider.of<BlocType>(context)
让子Widget
获取Bloc
; - 负责
Bloc
的生命周期管理;
scss
// 在Widget树顶层提供Bloc
BlocProvider(
create: (context) => CounterBloc(), // 创建Bloc实例
child: MaterialApp(home: CounterPage()),
);
// 在子Widget中获取Bloc
final counterBloc = BlocProvider.of<CounterBloc>(context);
4.1.3 BlocListener
核心特性:
- 仅在状态变化时触发一次(不会因父组件重建而重复执行)
- 不返回 Widget,仅用于执行副作用逻辑(异步操作)
- 可以通过listenWhen控制是否响应特定状态变化
kotlin
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
// 当登录成功时导航到首页
if (state is LoggedInState) {
Navigator.pushReplacementNamed(context, '/home');
}
// 当登录失败时显示错误提示
else if (state is LoginErrorState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage)),
);
}
},
// 仅在状态从非加载变为加载时响应
listenWhen: (previous, current) {
// 通过判断前一状态和当前状态,决定是否要响应这个状态的变化
return previous is! LoadingState && current is LoadingState;
},
child: LoginForm(), // BlocListener返回的子组件
)
4.1.4 BlocSelector
当 Bloc
的状态包含大量数据,但 UI
只依赖其中一部分时,使用 BlocSelector
可以仅监听状态中的特定字段,避免不必要的 UI
重建,提高性能,它通过 selector
回调从完整状态中提取所需数据,并通过equalityChecker
判断数据是否变化(默认使用==
比较)。只有当提取的数据确实变化时,才会重建 UI,关键特点为:
- 从复杂状态中提取所需的部分数据
- 只有当提取的数据发生变化时,才会触发重建
- 减少不必要的 Widget 重建,优化性能
javascript
// 假设个人中心的`UI` 只需显示用户名,使用 `BlocSelector` 可以避免在 isLoading 或 error 变化时重建
BlocSelector<UserBloc, UserState, String>(
// 提取所需的状态部分
selector: (state) => state.user.name,
// 只有当用户名变化时才会重建
builder: (context, userName) {
return Text('用户名: $userName');
},
)
4.1.5 BlocConsumer
BlocConsumer
是一个组合组件,它同时包含了BlocBuilder
和 BlocListener
的功能,既可以处理副作用,又可以构建 UI
。当需要在同一个Bloc
中同时处理这两种需求时,使用BlocConsumer
可以简化代码。几个核心特点为:
- 整合了
BlocBuilder
和BlocListener
的功能 - 提供
builder
回调用于构建UI
- 提供
listener
回调用于处理副作用 - 分别通过
buildWhen
和listenWhen
控制重建和监听的条件
kotlin
BlocConsumer<ProductBloc, ProductState>(
// 处理副作用
listener: (context, state) {
if (state is ProductAddedToCartState) {
// 展示一个提示信息
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('商品已加入购物车')),
);
}
},
// 构建UI
builder: (context, state) {
if (state is ProductLoadingState) {
// 加载中
return CircularProgressIndicator();
} else if (state is ProductLoadedState) {
// 正常商品
return ProductDetailView(product: state.product);
} else if (state is ProductErrorState) {
// 错误状态
return Text('加载失败: ${state.error}');
}
return Container();
},
// 控制何时重建UI
buildWhen: (previous, current) {
// 只有状态是加载、加载完成或错误时才重建
return current is ProductLoadingState ||
current is ProductLoadedState ||
current is ProductErrorState;
},
// 控制何时触发listener
listenWhen: (previous, current) {
// 只有当状态变为ProductAddedToCartState时才触发
return current is ProductAddedToCartState;
},
)
4.2 Bloc类的原理
底层设计基于 Dart 的异步编程模型和 Stream 流,其核心实现原理可总结为:
- 基于Dart Stream的响应式编程模型;
- 通过事件驱动的状态机模式;
- 利用InheritedWidget实现上下文传递
- 严格分离业务逻辑与UI层
5 总结
本文围绕Flutter开发中的状态管理框架Bloc展开,详细介绍了其核心概念、使用方法、相关库及一些关键类,旨在帮助开发者了解该开发框架,并在实际开发中使用该框架来应对复杂应用开发中的状态管理挑战。