一嘁字、Bloc 介紹
Bloc 的名字比較新穎纪蜒,這個狀態(tài)管理框架的目的是將 UI 層和業(yè)務(wù)邏輯進(jìn)行分離。Bloc 的復(fù)雜度處于 ScopedModel 和 Redux 之間随珠,相較于 ScopedModel窗看,Bloc 擁有分明的架構(gòu)處于業(yè)務(wù)邏輯倦炒,相較于 Redux逢唤,Bloc 著重于業(yè)務(wù)邏輯的分解涤浇,使得整個框架對于開發(fā)來講簡單實(shí)用芙代。
二纹烹、Bloc 的層次結(jié)構(gòu)
Bloc 分為三層:
- Data Layer(數(shù)據(jù)層)召边,用于提供數(shù)據(jù)隧熙。
- Bloc(Business Logic) Layer(業(yè)務(wù)層)贞盯,通過繼續(xù) Bloc 類實(shí)現(xiàn),用于處理業(yè)務(wù)邏輯闷愤。
- Presentation Layer(表現(xiàn)層)件余,用于 UI 構(gòu)建讥脐。
Presentation Layer 只與 Bloc Layer 交互,Data Laye 也只與 Bloc Layer 交互啼器。Bloc Layer 作為重要一層旬渠,處于表現(xiàn)層和數(shù)據(jù)層之間,使得 UI 和數(shù)據(jù)通過 Bloc Layer 進(jìn)行交互端壳。
由此可見告丢,Bloc 的架構(gòu)和客戶端主流的 MVC 和 MVP 架構(gòu)比較相似,但也存在 Event 和 State 的概念一同構(gòu)成響應(yīng)式框架损谦。
三岖免、Bloc 需要知道的概念
BlocProvider成翩,通常做為 App 的根布局觅捆。BlocProvider 可以保存 Bloc赦役,在其它頁面通過BlocProvider.of<Bloc>(context)
獲取 Bloc麻敌。
Event,用戶操作 UI 后發(fā)出的事件掂摔,用于通知 Bloc 層事件發(fā)生术羔。
State赢赊,頁面狀態(tài),可用于構(gòu)建 UI级历。通常是 Bloc 將接收到的 Event 轉(zhuǎn)化為 State释移。
Bloc 架構(gòu)的核心是 Bloc 類,Bloc 類是一個抽象類寥殖,有一個 mapEventToState(event)
方法需要實(shí)現(xiàn)玩讳。mapEventToState(event)
顧名思義,就是將用戶點(diǎn)擊 View 時發(fā)出的 event 轉(zhuǎn)化為構(gòu)建 UI 所用的 State嚼贡。另外熏纯,在 StatefulWidget 中使用 bloc 的話,在 widget dispose 時粤策,要調(diào)用 bloc.dispose()
方法進(jìn)行釋放樟澜。
四、Bloc 的實(shí)踐
這里以常見的獲取列表選擇列表為例子叮盘。一個頁面用于展示選中項(xiàng)和跳轉(zhuǎn)到列表秩贰,一個頁面用于顯示列表。
- 引入 Redux 的第三方庫
在 pubspec.yaml
文件中引入 flutter_bloc
第三方庫支持 bloc 功能柔吼。
# 引入 bloc 第三方庫
flutter_bloc: ^0.9.0
- 使用 Bloc 插件
這一步可有可無毒费,但使用插件會方便開發(fā),不使用的話也沒什么問題嚷堡。
Bloc 官方提供了 VSCode 和 Android studio 的插件蝗罗,方便生成 Bloc 框架用到的相關(guān)類。
下文以 Android studio 的插件為例蝌戒。
比如 list 頁面串塑,該插件會生成相應(yīng)的類
從生成的五個文件中也可以看到,list_bloc
負(fù)責(zé)承載業(yè)務(wù)邏輯北苟,list_page
負(fù)責(zé)編寫 UI 界面桩匪,list_event
和 list_state
分別是事件和狀態(tài),其中 list.dart
文件是用于導(dǎo)出前面四個文件的友鼻。
具體使用可見
- 使用 BlocProvider 作為根布局
在 main.dart
中傻昙,使用 BlocProvider 作為父布局包裹,用于傳遞需要的 bloc彩扔。Demo 中包含兩個頁面妆档,一個是展示頁面 ShowPage,一個是列表頁面 ListPage虫碉。
上面講到贾惦,Bloc 的核心功能在于 Bloc 類,對于展示頁面 ShowPage,會有一個 ShowBloc 繼續(xù)自 Bloc 類须板。由于展示頁面 ShowPage 會和列表頁面 ListPage 有數(shù)據(jù)的互動碰镜,所以這里將 ShowBloc 保存在 BlocProvider 中進(jìn)行傳遞。
@override
Widget build(BuildContext context) {
return BlocProvider(
bloc: _showBloc,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ShowPage()));
}
- 展示頁面 ShowPage
① ShowEvent
列表的 item 點(diǎn)擊后习瑰,需要發(fā)送一個 event 通知其它頁面列表被選中绪颖,這里定義一個 SelectShowEvent 作為這種 event 通知。
class SelectShowEvent extends ShowEvent {
String selected;
SelectShowEvent(this.selected);
}
② ShowState
State 用于表示一種界面狀態(tài)甜奄,即一個 State 就對應(yīng)一個界面柠横。插件在一開始會生成一個默認(rèn)狀態(tài),InitialShowState课兄。我們可以使用 InitialShowState 來代表初始的界面滓鸠。另外,我們自己定義一種狀態(tài)第喳,SelectedShowState糜俗,代表選中列表后的 State。
@immutable
abstract class ShowState {}
class InitialShowState extends ShowState {}
class SelectedShowState extends ShowState {
String _selectedString = "";
String get selected => _selectedString;
SelectedShowState(this._selectedString);
}
③ ShowBloc
Bloc 的主要職責(zé)是接收 Event曲饱,然后把 Event 轉(zhuǎn)化為對應(yīng)的 State悠抹。這里的 ShowBloc 繼續(xù)自 Bloc,需要重寫實(shí)現(xiàn)抽象方法 mapEventToState(event)
扩淀。在這個方法中楔敌,我們判斷傳過來的 event 是不是 SelectShowEvent,是則拿到 SelectShowEvent 中的 selected 變量去構(gòu)建 SelectedShowState驻谆。mapEventToState(event)
返回的是一個 Stream卵凑,我們通過 yield 關(guān)鍵字去返回一個 SelectedShowState。
class ShowBloc extends Bloc<ShowEvent, ShowState> {
@override
ShowState get initialState => InitialShowState();
@override
Stream<ShowState> mapEventToState(
ShowEvent event,
) async* {
if (event is SelectShowEvent) {
yield SelectedShowState(event.selected);
}
}
}
④ ShowPage
在 ShowPage 的界面上胜臊,我們需要根據(jù) showBloc 中是否有被選中的列表項(xiàng)目去展于頁面勺卢,所以這里我們先使用使用BlocProvider.of<ShowBloc>(context)
去拿到 showBloc,接著再用 BlocBuilder 根據(jù) showBloc 構(gòu)建界面象对。使用 BlocBuilder 的好處就是可以讓頁面自動響應(yīng) showBloc 的變化而變化黑忱。
var showBloc = BlocProvider.of<ShowBloc>(context);
...
BlocBuilder(
bloc: showBloc,
builder: (context, state) {
if (state is SelectedShowState) {
return Text(state.selected);
}
return Text("");
}),
- 列表頁面 ListPage
① ListEvent
列表頁面,我們一開始需要從網(wǎng)絡(luò)中拉取列表數(shù)據(jù)勒魔,所以定義一個 FetchListEvent 事件在進(jìn)入頁面時通知 ListBloc 去獲取列表甫煞。
@immutable
abstract class ListEvent extends Equatable {
ListEvent([List props = const []]) : super(props);
}
class FetchListEvent extends ListEvent {}
② ListState
InitialListState 是插件默認(rèn)生成的初始狀態(tài),另外定義一個 FetchListState 代表獲取列表完成的狀態(tài)冠绢。
@immutable
abstract class ListState extends Equatable {
ListState([List props = const []]) : super(props);
}
class InitialListState extends ListState {}
class FetchListState extends ListState {
List<String> _list = [];
UnmodifiableListView<String> get list => UnmodifiableListView(_list);
FetchListState(this._list);
}
③ ListBloc
在 ListBloc 中抚吠,進(jìn)行從網(wǎng)絡(luò)獲取列表數(shù)據(jù)的業(yè)務(wù)。這里通過一個延時操作摸擬網(wǎng)絡(luò)請求弟胀,最后用 yield 返回列表數(shù)據(jù)楷力。
class ListBloc extends Bloc<ListEvent, ListState> {
@override
ListState get initialState => InitialListState();
@override
Stream<ListState> mapEventToState(
ListEvent event,
) async* {
if (event is FetchListEvent) {
// 模擬網(wǎng)絡(luò)請求
await Future.delayed(Duration(milliseconds: 2000));
var list = [
"1. Bloc artitechture",
"2. Bloc artitechture",
"3. Bloc artitechture",
"4. Bloc artitechture",
"5. Bloc artitechture",
"6. Bloc artitechture",
"7. Bloc artitechture",
"8. Bloc artitechture",
"9. Bloc artitechture",
"10. Bloc artitechture"
];
yield FetchListState(list);
}
}
}
④ ListPage
在列表頁面初始化時有兩個操作蕊玷,一個是初始化 listBloc,一個是發(fā)出列表請求的 Event弥雹。
@override
void initState() {
bloc = ListBloc(); // 初始化listBloc
bloc.dispatch(FetchListEvent()); // 發(fā)出列表請求事件
super.initState();
}
接下用,便是用 BlocBuilder 去響應(yīng)狀態(tài)延届。當(dāng) state 是 InitialListState剪勿,說明未獲取列表,則顯示 loading 界面方庭,當(dāng) state 是 FetchListState 時厕吉,說明已經(jīng)成功獲取列表,顯示列表界面械念。
body: BlocBuilder(
bloc: bloc,
builder: (context, state) {
// 根據(jù)狀態(tài)顯示界面
if (state is InitialListState) {
// 顯示 loading 界面
return buildLoad();
} else if (state is FetchListState) {
// 顯示列表界面
var list = state.list;
return buildList(list);
}
}));
最后头朱,記得對 bloc 進(jìn)行 dispose()
。
@override
void dispose() {
bloc.dispose();
super.dispose();
}
具體代碼可以到 github 查看龄减。
總結(jié)
在 Bloc 的架構(gòu)中项钮,將一個頁面和一個 Bloc 相結(jié)合,由頁面產(chǎn)生 Event希停,Bloc 根據(jù)業(yè)務(wù)需要將 Event 轉(zhuǎn)化為 State烁巫,再把 State 交給頁面中的 BlocBuilder 構(gòu)建 UI。Demo 中只是給出了簡單的狀態(tài)管理宠能,實(shí)際項(xiàng)目中亚隙,比如網(wǎng)絡(luò)請求,有請求中违崇、請求成功阿弃、請求失敗的多種狀態(tài),可以做適當(dāng)封裝使 Bloc 更加易用羞延。相比于 Redux渣淳,Bloc 不需要將所有狀態(tài)集中管理,這樣對于不同模塊的頁面易于拆分伴箩,對于代碼量比較大的客戶端而言水由,Bloc 的架構(gòu)會相對比較友好。