一、狀態管理難題
在Flutter中有一個很有爭論的部分——揪淨該怎樣狀態管理
我一開始堅持移動端的思路,使用MVC或者MVP等,還有的同學支援前端思路,使用Redux或者RxDart等,也有人說Google推薦使用BLoC,還有像我翻譯的一篇文章一樣:[譯]讓我來幫你理解和選擇Flutter狀態管理方案, 使用Redux和BLoC的混合。
- MVC和MPV中狀態和重新整理是分離的
- Redux把所有狀態存到一起有些臃腫,也會存在UI不必要的重新整理,這在App上需要儘量避免
- BLoC是使用Stream或者RxDart等工具,將資料(狀態)獨立出去,然後當狀態有更新的時候,資料使用者自動更新
我一直在想,能不能儘量使用在iOS上開發的方式來做,後來發現行不通,原因是flutter的渲染方式和移動端完全不同,它採用的React的思路。
移動端的UI控制元件可以通過修改其屬性改變外觀,但是flutter和RN,改變樣式基本是靠重新渲染,所以想要更新內容,就要改變state,然後再通過setState()更新UI。
所以flutter裡更新UI,先天是割裂的,這一點和React一樣,所以就需要觀察者(或是什麼類似的)設計模式的封裝,來降低更新UI的複雜程度,減少耦合或者過多的狀態宣告。
二、BLoC介紹
說了這麼多,其實就是想說,BLoC還是值得一試的,他能解決狀態和UI揉雜在一起的問題,也沒有Redux這麼重,適合用於簡單業務場景的資料同步。
BLoC是一種設計模式,官方並沒有給出封裝的程式碼,網上搜到的程式碼大同小異(不知道是不是Google給的最佳實踐),但是他們的共同特點就是,初始值賦值上有問題,UI的初始值,沒有用BLoC的資料部分給出的初始值,只是恰好兩者值相等,那當需要改變初始值的時候,就需要改很多處,這顯然不可接受。(原因這裡就不再贅述了)
剛剛也說了,BLoC的實現,使用了Stream或者RxDart,我傾向於使用Stream,因為它是內建的庫,RxDart功能我沒有評估,但是顯然針對簡單業務場景,過於重了。
關於BLoC的實現細節,這裡推薦大家看鑫磊的文章:Flutter | 狀態管理探索篇——BLoC(三),我在這裡不詳細說
BLoC的結構如下:
我也不知道這應該歸類為觀察者模式,還是生產者消費者模式,總之是:
-
BLoC資料模組,持有一個StreamController,來管理stream
-
UI需要展示資料的地方,使用StreamBuilder來監聽stream變化
-
當stream裡的資料變化時,就會自動重新整理子UI。
-
需要更新資料時,比如點選了某個按鈕,則會操作BLoC資料模組,通過其StreamController更新裡面的資料,這樣UI就會自動重新整理了。
三、BLoC封裝
不知不覺囉嗦了這麼多,其實這篇文章的目的是對BLoC進行封裝,使Stream類不再暴露在業務程式碼中。
Pub倉庫:
封裝分成三部分:
RLKBaseBLoC: 資料類,儲存了任意型別的資料data(範型),還有一個改變資料的changeData方法。data供資料使用者來使用,而資料操作者使用changeData改變資料。
你可以繼承它來定義自己的資料內容,同時增加一些方法,用於更新資料。 建立資料例項很簡單,把要儲存的初始資料傳進建構函式就可以了:
class CountBLoC extends RLKBaseBLoC<int> {
//RLKBaseBLoC子類,增加了自加方法
CountBLoC(int data) : super(data);
increment() {
changeData(data + 1);
}
}
CountBLoC(0);//RLKBaseBLoC例項
複製程式碼
RLKBloCProvider: 這個是一個Widget,儲存了RLKBaseBLoC例項,example中它包住了整個MaterialApp,它的作用就是為需要用到資料的地方提供資料來源,它只要是所有使用到RLKBaseBLoC資料的widegt的共用根節點就可以。如果你的RLKBaseBLoC資料,只是在頁面A中有很多地方展示,那麼RLKBloCProvider只需要包住頁面A。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RLKBloCProvider(//包住了整個APP,因為資料要被兩個頁面使用
bloc: CountBLoC(0),//儲存了RLKBaseBLoC例項
child: MaterialApp(
theme: ThemeData.light(),
home: TopPage(),
),
);
}
}
複製程式碼
RLKBLoCBuilder: 當需要使用RLKBaseBLoC裡面的資料的時候,就在這個widget外圍包住RLKBLoCBuilder,它的作用就是給你提供RLKBaseBLoC資料例項,比如example中,儲存RLKBaseBLoC例項的RLKBloCProvider放在了main.dart中,但是當我想在TopPage中使用RLKBaseBLoC例項的時候,是無法直接拿到這個例項的,通過RLKBLoCBuilder,就可以在buider方法中,以引數的形式拿到RLKBaseBLoC例項了。
Center(child: RLKBLoCBuilder(builder: (BuildContext context, int data, RLKBaseBLoC bloc) {
return Text(
'You hit me: $data times',
);
}))
複製程式碼
最後,當你想更新資料的時候,同樣是通過RLKBLoCBuilder拿到RLKBaseBLoC例項,然後對資料進行操作
class _STFState extends State<UnderPage> {
@override
Widget build(BuildContext context) {
return RLKBLoCBuilder(builder: (BuildContext context, int data, RLKBaseBLoC bloc) {
CountBLoC bloc2 = bloc as CountBLoC;
return Scaffold(
appBar: AppBar(
title: Text('Under Page'),
),
body: Center(
child: Text(
"You hit me: $data times",
style: Theme.of(context).textTheme.display1,
)),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {});
bloc2.increment();//改變資料
},
child: Icon(Icons.add),
),
);
});
}
}
複製程式碼
四、總結
當你想要更靈活地管理狀態和UI的時候,直接使用這個package可以讓你遠離複雜的Redux、RxDart、Stream等概念,只需要一個資料類和兩個容器Widegt就可以了,資料和UI很好的分離,庫對程式碼的入侵也比較少。希望可以幫助到你。