BLoC(Business Logic Component,业务逻辑组件)是 Flutter 中常用的一种状态管理模式。它的核心思想是将 业务逻辑 与 UI 逻辑 分离,使得应用的状态管理更加清晰和易于维护。下面是 BLoC 的基本概念及其工作原理
1. BLoC 模式的核心思想
BLoC 模式的核心思想就是将 事件(Events) 和 状态(States) 分开,并通过 事件 来驱动 状态的变化,然后 通过新的状态来更新界面。这种模式能够帮助你把业务逻辑与 UI 逻辑隔离开来,让代码更加模块化和可维护
2. 主要组成部分
在 BLoC 模式中,主要有三个组成部分:
- 事件(Event)
- 状态(State)
- BLoC(业务逻辑组件)
3. 事件(Event)
- 事件 是用户操作或外部系统的输入,通常由 UI 触发。比如:点击按钮、获取数据、输入文本等。
- 事件通常是不可变的(immutable),一旦创建就无法更改。
- 在 BLoC 模式中,事件用于通知 BLoC 执行某种操作。
例如:
- 用户点击了按钮:
ButtonPressedEvent - 用户请求加载数据:
FetchDataEvent
4. 状态(State)
- 状态 描述了应用的某一时刻的显示或数据状态。UI 的显示是基于状态的,状态的变化决定了 UI 的变化。
- 每次 BLoC 处理完事件并做出相应的更新后,都会生成一个新的状态,并通过
emit发出这个状态,供 UI 更新。
例如:
- 计数器应用中的当前计数:
CounterState(count: 5) - 数据加载中:
LoadingState() - 数据加载成功:
LoadedState(data: [1, 2, 3])
5. BLoC(业务逻辑组件)
-
BLoC 是处理事件和生成状态的核心部分,它接收事件并执行相关的业务逻辑,然后根据需要生成新的状态。
-
BLoC 类通常包含以下两部分:
- 监听事件:BLoC 类会监听各种类型的事件。
- 更新状态:每当一个事件触发时,BLoC 会根据事件的内容计算出一个新的状态并将其发射出来。
6. 如何工作?
BLoC 的工作流程通常是这样的:
- 事件(Event) 触发:UI 中的用户操作或其他条件触发了某个事件(例如按钮点击、数据请求)。
- BLoC 处理事件:BLoC 接收到这个事件后,根据事件中的数据进行业务逻辑处理(例如,更新数据、做某些计算等)。
- BLoC 发出新的状态(State) :BLoC 根据事件的处理结果,生成一个新的状态,并通过
emit发出这个状态。
// 注册事件与事件处理函数的映射
on<PainterSourceChooseEvent>(_onPainterSourceChooseEvent);
void _onPainterSourceChooseEvent(PainterSourceChooseEvent event, Emitter<PopupPaintSettingState> emit) {
debugPrint('PainterSourceChooseEvent: ${event.operationMode.toString()}');
emit(state.clone());
}
- UI 更新:UI 监听状态的变化,收到新的状态后更新界面,显示新的内容。
7. 示例:简单的计数器应用
假设我们有一个简单的计数器应用,用户点击按钮增加计数。使用 BLoC 模式的流程如下:
//事件
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
//状态
abstract class CounterState {}
class CounterInitialState extends CounterState {
final int count;
CounterInitialState(this.count);
}
//BLOC类
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitialState(0));
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is IncrementEvent) {
final currentState = state as CounterInitialState;
yield CounterInitialState(currentState.count + 1);// 新的状态
}
}
}
//UI页面
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
if (state is CounterInitialState) {
return Scaffold(
appBar: AppBar(title: Text("Counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Count: ${state.count}"),
ElevatedButton(
onPressed: () {
// 触发事件,增加计数
context.read<CounterBloc>().add(IncrementEvent());
},
child: Text("Increment"),
),
],
),
),
);
}
return CircularProgressIndicator();
},
);
}
---------------------------------------------------------------------------------------
1.使用抽象类来定义事件(如 `CounterEvent` CounterState)的主要原因是为了实现 **事件类型的封装** 和 **继承扩展**。这样做有几个好处
**扩展性和维护性**
**事件统一管理**
// 举个例子,使用一个事件流来管理
StreamController<CounterEvent> _eventController = StreamController<CounterEvent>();
// 监听事件
_eventController.stream.listen((event) {
if (event is IncrementEvent) {
// 处理 IncrementEvent
} else if (event is DecrementEvent) {
// 处理 DecrementEvent
}
});
2.mapEventToState
- **`mapEventToState`**:是处理事件的核心方法,它会接收事件并生成新的状态。你不需要在
这里注册事件,`mapEventToState` 会监听所有的事件类型并根据事件进行处理。
- **`on<EventType>`** :是注册事件和事件处理函数的简便方法,它可以让你将事件处理逻辑
拆分到单独的函数中,增强代码的可读性和可维护性。
3.在 Flutter 中,`Bloc` 状态管理的常见使用方法包括 `BlocProvider`、`BlocBuilder` 和
`MultiBlocProvider`,这些是最常用的,但还有其他一些方法和工具可以帮助你在应用中使用
`Bloc` 更加高效
1).
BlocProvider<CounterBloc>(
create: (BuildContext context) => CounterBloc(),
child: CounterPage(),
);
- `create` 是用来创建 `Bloc` 的方法。
- `child` 是你想要传递 `Bloc` 的 widget。
2).
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
if (state is CounterInitialState) {
return Text('Count: ${state.count}');
}
return CircularProgressIndicator();
},
);
3).
MultiBlocProvider(
providers: [
BlocProvider<CounterBloc>(create: (_) => CounterBloc()),
BlocProvider<UserBloc>(create: (_) => UserBloc()),
],
child: MyApp(),
);
4).### `BlocListener`
`BlocListener` 用来监听 `Bloc` 的状态并执行副作用操作(例如导航、显示 Snackbar 等)。与
`BlocBuilder` 不同,`BlocListener` 不会重新构建 UI,只会触发副作用。
BlocListener<CounterBloc, CounterState>(
listener: (context, state) {
if (state is CounterInitialState && state.count == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count is 10')),
);
}
},
child: CounterPage(),
);
5).BlocConsumer
`BlocConsumer` 是 `BlocBuilder` 和 `BlocListener` 的组合。它既可以用来构建 UI,也可
以用来监听状态并执行副作用操作。适合在同一个组件中需要同时进行 UI 更新和副作用处理的场景。
- `listener` 用来处理副作用。
- `builder` 用来构建 UI
BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) {
if (state is CounterInitialState && state.count == 10) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count is 10')),
);
}
},
builder: (context, state) {
if (state is CounterInitialState) {
return Text('Count: ${state.count}');
}
return CircularProgressIndicator();
},
);
6).BlocSelector
`BlocSelector` 用于从 `Bloc` 中选择和监听特定的状态片段,它能够避免不必要的 UI 更新。通
常用于状态比较大的 `Bloc`,你只关心其中的一部分数据时非常有用
- `selector` 用来选择你关心的 `state` 的一部分。
- `builder` 会在 `state` 变化时重建 UI,但只有当选择的数据发生变化时才会更新
BlocSelector<CounterBloc, CounterState, int>(
selector: (state) {
if (state is CounterInitialState) {
return state.count;
}
return 0;
},
builder: (context, count) {
return Text('Count: $count');
},
);
7).`BlocProvider.of` / `context.read` / `context.watch`
你可以直接通过 `BlocProvider.of<T>(context)` 来访问 `Bloc` 实例并触发事件。例如:
BlocProvider.of<CounterBloc>(context).add(IncrementEvent());
`context.read<T>()`:
context.read<CounterBloc>().add(IncrementEvent());
#### `context.watch<T>()`:
`context.watch<T>()` 会在 `T` 发生变化时自动重新构建 UI,相当于使用 `BlocBuilder` 来
监听 `Bloc` 的变化。
final state = context.watch<CounterBloc>().state;
8).BlocProvider.value
是一种通过已存在的 `Bloc` 实例来提供 `Bloc` 给 widget 树的方式。它通常用于已经创建并管
理好的 `Bloc` 实例,避免每次创建新的 `Bloc` 实例。
BlocProvider.value(
value: myExistingBloc,
child: CounterPage(),
);
9. 总结
- BLoC 模式 通过将 事件 和 状态 分开,使得业务逻辑和 UI 逻辑解耦,从而提高代码的可维护性和可测试性。
- 事件表示用户的行为或其他触发条件,状态表示应用的当前状态。
- BLoC 负责接收事件,处理并生成新的状态,然后通知 UI 更新。
- 看到这就可以看懂别人的BLoC代码了。也可以写自己的逻辑了。