BLoC 单向数据流完整教程
目录
- [什么是 BLoC](#什么是 BLoC)
- [为什么选择 BLoC](#为什么选择 BLoC)
- 核心概念
- 环境安装
- 计数器示例
- 网络请求示例
- [与 GoRouter 集成](#与 GoRouter 集成)
- 最佳实践
什么是 BLoC
BLoC(Business Logic Component,业务逻辑组件)是一种将业务逻辑与 UI 分离的状态管理架构。它通过**事件(Event)驱动状态(State)**的变化,UI 只负责展示状态并派发事件。
BLoC 的核心设计目标:
- UI 只显示状态,不直接操作数据
- 业务逻辑集中在 BLoC 中,便于测试和维护
- 数据流向单一:Event → BLoC → State → UI
为什么选择 BLoC
| 特性 | setState | Riverpod | BLoC |
|---|---|---|---|
| 适用场景 | 简单页面 | 中小型应用 | 中大型应用 |
| 业务与 UI 分离 | 弱 | 中 | 强 |
| 可测试性 | 低 | 高 | 高 |
| 学习曲线 | 低 | 中 | 高 |
适合使用 BLoC 的场景:
- 复杂的表单状态管理
- 多步骤的业务流程
- 需要严格状态机的页面
- 团队协作的大型项目
核心概念
BLoC 的单向数据流:
UI 派发事件
↓
Event 进入 BLoC
↓
BLoC 处理业务逻辑
↓
State 更新
↓
UI 重建
核心组件
- Event :用户操作或系统通知,如
CounterIncrementPressed - State :页面当前的状态,如
CounterState(count: 1) - Bloc:接收事件,处理逻辑,发射新状态
- UI:监听状态,派发事件
环境安装
在 pubspec.yaml 中添加:
yaml
dependencies:
flutter_bloc: ^8.1.4
执行:
bash
flutter pub get
计数器示例
定义 State
dart
class CounterState {
final int count;
const CounterState({required this.count});
CounterState copyWith({int? count}) {
return CounterState(count: count ?? this.count);
}
}
定义 Event
dart
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}
定义 Bloc
dart
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(count: 0)) {
on<CounterIncrementPressed>((event, emit) {
emit(state.copyWith(count: state.count + 1));
});
on<CounterDecrementPressed>((event, emit) {
emit(state.copyWith(count: state.count - 1));
});
}
}
UI 使用
dart
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: const CounterView(),
);
}
}
class CounterView extends StatelessWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context) {
final bloc = context.read<CounterBloc>();
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Count: ${state.count}');
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => bloc.add(CounterIncrementPressed()),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => bloc.add(CounterDecrementPressed()),
child: const Icon(Icons.remove),
),
],
),
);
}
}
网络请求示例
定义状态
dart
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
UserLoaded(this.user);
final User user;
}
class UserError extends UserState {
UserError(this.message);
final String message;
}
定义事件
dart
abstract class UserEvent {}
class UserFetched extends UserEvent {
UserFetched(this.userId);
final String userId;
}
定义 Bloc
dart
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc(this._userRepository) : super(UserInitial()) {
on<UserFetched>(_onUserFetched);
}
final UserRepository _userRepository;
Future<void> _onUserFetched(
UserFetched event,
Emitter<UserState> emit,
) async {
emit(UserLoading());
try {
final user = await _userRepository.fetchUser(event.userId);
emit(UserLoaded(user));
} catch (e) {
emit(UserError(e.toString()));
}
}
}
UI 使用
dart
class UserPage extends StatelessWidget {
const UserPage({required this.userId, super.key});
final String userId;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => UserBloc(UserRepository())..add(UserFetched(userId)),
child: const UserView(),
);
}
}
class UserView extends StatelessWidget {
const UserView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('User')),
body: BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
return switch (state) {
UserInitial() => const Center(child: Text('请点击加载')),
UserLoading() => const Center(child: CircularProgressIndicator()),
UserLoaded(:final user) => Center(child: Text(user.name)),
UserError(:final message) => Center(child: Text('错误: $message')),
};
},
),
);
}
}
与 GoRouter 集成
BLoC 可以放在路由上层或页面内。页面级 BLoC 通常使用 BlocProvider 包裹。
dart
GoRoute(
path: '/users/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return BlocProvider(
create: (_) => UserBloc(UserRepository())..add(UserFetched(userId)),
child: const UserPage(),
);
},
),
最佳实践
- State 不可变 :使用
copyWith创建新状态,不要直接修改字段。 - Event 命名清晰 :使用
动词+名词+时态命名,如LoginSubmitted。 - Bloc 只处理逻辑:UI 不直接调用 Repository,只派发事件。
- 使用 sealed class 表示状态 :利用 Dart 3 的
switch表达式进行穷尽匹配。 - 单一职责:一个 Bloc 管理一个功能模块的状态。
- 及时关闭 Bloc :
BlocProvider会自动处理,无需手动 dispose。
结语
BLoC 通过严格的单向数据流,将业务逻辑从 UI 中彻底抽离。虽然学习曲线较高,但在复杂应用中能提供清晰的架构和优秀的可测试性。掌握 BLoC 后,可以结合 Dio、GoRouter、Riverpod 等工具构建结构良好的 Flutter 应用。