Flutter 86: 初识状态管理 Bloc (一)
小菜以前尝试过 provider 状态管理工具,简单便捷;但在新的项目中,相关同学采用的是 Bloc 状态管理工具,且前段时间何时简单了解了 Stream 相关知识,因此趁机学习一下基本的 Bloc 相关的内容;
Bloc
Bloc 可以方便的把样式与业务逻辑区分开,从而使开发更便捷,可重用性更好,测试也更加方便;
在使用 Bloc 之前需要提前了解一下如下几个概念;
Event
Event 事件作为 Bloc 的输入,一般是为了响应用户交互(例如按钮按下)或生命周期事件(例如页面加载)而添加它们。可以使用枚举类型定义事件类,对于相对复杂的事件可以联合业务定义为 class;
enum TestEvent { onEvent1, onEvent2 ... onEventN}
States
States 状态作为 Bloc 的输出,一般用于 UI 状态的更新,页面更新绘制等;一般需要定义不同的数据类型来表示数据状态的变更;
class TestState {
final int state1;
final String state2;
...
final User stateN;
TestState(this.state1, this.state2 ... this.stateN);
}
Transitions
Transitions 转场作为从一个状态到下一个状态的过度,过渡由当前状态,事件和下一个状态组成;例如小菜上述定义的 TestEvent 中的各个 onEvent 中状态变更等均可以视为 Transitions 转场;onTransition 在 Bloc 的 state 更新之前被调用,常用于记录 Bloc 日志和分析;
@override
void onTransition(Transition<NumberEvent, int> transition) {
print('====onTransition===${transition}');
super.onTransition(transition);
}
Streams
作为异步数据传输;其中使用 async* 时可以使用 yield 关键字并返回一个 Stream 数据;
Stream<int> testStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
Blocs
Bloc 作为将 Stream 输入的 Event 事件转换为输出的 States 状态;每个自定义的 Bloc 必须继承自基础的 Bloc;通过复写 initialState 和 mapEventToState 方法来完成事件 Event 和 State 状态的转换;
initialState 为 Bloc 初始化状态,该状态是接收任何事件之前的状态;
mapEventToState 将 Event 作为参数,返回的是 Stream 的 state 状态,通过 state 属性随时访问当前的块状态;
每个 Bloc 都有一个 add 方法,用来添加新的 Event 到 mapEventToState 中;
Bloc 通过 onError 方法获取异常信息并处理等;
class TestBloc extends Bloc<TestEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(TestEvent event) async* {
switch (event) {
case TestEvent.onEvent1:
yield state - 1;
break;
case TestEvent.onEvent2:
yield state + 1;
break;
}
}
@override
void onError(Object error, StackTrace stackTrace) {
print('====onError===$error, $stackTrace');
super.onError(error, stackTrace);
}
}
BlocDelegate
BlocDelegate 为 Bloc 的委托,可以在全局或需要的作用域范围内统一管理 onTransition 和 onError 等;
class NumberBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
print('====Delegate.onEvent===$bloc====$event');
super.onEvent(bloc, event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
print('====Delegate.onTransition===$bloc====$transition');
super.onTransition(bloc, transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stackTrace) {
print('====Delegate.onError===$error, $stackTrace');
super.onError(bloc, error, stackTrace);
}
}
TestCode
小菜尝试了最简单的 Bloc,点击按钮会数字会递增,目前更新 UI 是通过 setState() 方式更新数据,在下一节中会尝试用 FlutterBloc 方式进行数据更新;
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
class BlocPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _BlocPageState();
}
class _BlocPageState extends State<BlocPage> {
var _number = 0;
NumberBloc _bloc = NumberBloc();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Page')),
body: Center(
child: Text('当前 Number = $_number\n下次 Number = ${_bloc.state}',
style: TextStyle(fontSize: 20.0, color: Colors.blue))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_bloc.add(NumberEvent.addEvent);
setState(() => _number = _bloc.state);
}));
}
}
enum NumberEvent { addEvent }
class NumberBloc extends Bloc<NumberEvent, int> {
@override
int get initialState => 10;
@override
Stream<int> mapEventToState(NumberEvent event) async* {
switch (event) {
case NumberEvent.addEvent:
yield state + 5;
break;
}
}
@override
void onTransition(Transition<NumberEvent, int> transition) {
print('====onTransition===${transition}');
super.onTransition(transition);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('====onError===$error, $stackTrace');
super.onError(error, stackTrace);
}
}
小菜初步体验了 Bloc,初步感觉比 Provider 稍微复杂一些,但是分工更为明确;而小菜对 Bloc 的 应用还不够熟练,下一节重点尝试 FlutterBloc 对于 UI 的数据更新等;如有错误,请多多指导!
来源: 阿策小和尚