原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier Boelens 為 Reactive Programming - Streams - BLoC 寫(xiě)的后續(xù)
閱讀本文前建議先閱讀前篇,前篇中文翻譯有兩個(gè)版本:
[譯]Flutter響應(yīng)式編程:Streams和BLoC by JarvanMo
忠于原作的版本Flutter中如何利用StreamBuilder和BLoC來(lái)控制Widget狀態(tài) by 吉原拉面
省略了一些初級(jí)概念,補(bǔ)充了一些個(gè)人解讀
前言
在了解 BLoC, Reactive Programming 和 Streams 概念后星著,我又花了些時(shí)間繼續(xù)研究牵触,現(xiàn)在非常高興能夠與你們分享一些我經(jīng)常使用并且個(gè)人覺(jué)得很有用的模式(至少我是這么認(rèn)為的)处铛。這些模式為我節(jié)約了大量的開(kāi)發(fā)時(shí)間错蝴,并且讓代碼更加易讀和調(diào)試果覆。
目錄
(由于原文較長(zhǎng)呜笑,翻譯發(fā)布時(shí)進(jìn)行了分割)
BlocProvider 性能優(yōu)化
結(jié)合 StatefulWidget 和 InheritedWidget 兩者優(yōu)勢(shì)構(gòu)建 BlocProviderBLoC 的范圍和初始化
根據(jù) BLoC 的使用范圍初始化 BLoC事件與狀態(tài)管理
基于事件(Event) 的狀態(tài) (State) 變更響應(yīng)表單驗(yàn)證
根據(jù)表單項(xiàng)驗(yàn)證來(lái)控制表單行為 (范例中包含了表單中常用的密碼和重復(fù)密碼比對(duì))Part Of 模式
允許組件根據(jù)所處環(huán)境(是否在某個(gè)列表/集合/組件中)調(diào)整自身的行為
文中涉及的完整代碼可在 GitHub 查看夫否。
2. BLoC 的范圍和初始化
要回答「要在哪初始化 BLoC?」這個(gè)問(wèn)題叫胁,需要先搞清楚 BLoC 的可用范圍(scope)凰慈。
2.1. 應(yīng)用中任何地方可用
在實(shí)際應(yīng)用中,常常需要處理如用戶(hù)鑒權(quán)驼鹅、用戶(hù)檔案微谓、用戶(hù)設(shè)置項(xiàng)、購(gòu)物籃等等需要在 App 中任何組件都可訪(fǎng)問(wèn)的數(shù)據(jù)或狀態(tài)输钩,這里總結(jié)了適用這種情況的兩種 BLoC 方案:
2.1.1. 全局單例 (Global Singleton)
這種方案使用了一個(gè)不在Widget視圖樹(shù)中的 Global 對(duì)象豺型,實(shí)例化后可用供所有 Widget 使用。
import 'package:rxdart/rxdart.dart';
class GlobalBloc {
///
/// Streams related to this BLoC
///
BehaviorSubject<String> _controller = BehaviorSubject<String>();
Function(String) get push => _controller.sink.add;
Stream<String> get stream => _controller;
///
/// Singleton factory
///
static final GlobalBloc _bloc = new GlobalBloc._internal();
factory GlobalBloc(){
return _bloc;
}
GlobalBloc._internal();
///
/// Resource disposal
///
void dispose(){
_controller?.close();
}
GlobalBloc globalBloc = GlobalBloc();
要使用全局單例 BLoC买乃,只需要 import 后調(diào)用定義好的方法即可:
import 'global_bloc.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
globalBloc.push('building MyWidget'); //調(diào)用 push 方法添加數(shù)據(jù)
return Container();
}
}
如果你想要一個(gè)唯一的姻氨、可從應(yīng)用中任何組件訪(fǎng)問(wèn)的 BLoC 的話(huà),這個(gè)方案還是不錯(cuò)的剪验,因?yàn)椋?/p>
- 簡(jiǎn)單易用
- 不依賴(lài)任何 BuildContext
- 當(dāng)然也不需要通過(guò) context 查找 BlocProvider 的方式來(lái)獲取 BLoC
- 釋放資源也很簡(jiǎn)單肴焊,只需將 application Widget 基于 StatefulWidget 實(shí)現(xiàn),然后重寫(xiě)其 dispose() 方法功戚,在 dispose() 中調(diào)用 globalBloc.dispose() 即可
我也不知道具體是為啥娶眷,很多較真的人反對(duì)全局單例方案,所以…我們?cè)賮?lái)看另一種實(shí)現(xiàn)方案吧…
2.1.2. 注入到視圖樹(shù)頂層
在 Flutter 中啸臀,包含所有頁(yè)面的ancestor本身必須是 MaterialApp 的父級(jí)茂浮。 這是因?yàn)轫?yè)面(或者說(shuō)Route)其實(shí)是作為所有頁(yè)面共用的 Stack 中的一項(xiàng),被包含在 OverlayEntry 中的壳咕。
換句話(huà)說(shuō)席揽,每個(gè)頁(yè)面都有自己獨(dú)立于任何其它頁(yè)面的 Buildcontext。這也解釋了為啥不用任何技巧是沒(méi)辦法實(shí)現(xiàn)兩個(gè)頁(yè)面(或路由)之間數(shù)據(jù)共享的谓厘。
因此幌羞,必須將 BlocProvider 作為 MaterialApp 的父級(jí)才能實(shí)現(xiàn)在應(yīng)用中任何位置都可使用 BLoC,如下所示:
void main() => runApp(Application());
class Application extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
bloc: AuthenticationBloc(),
child: MaterialApp(
title: 'BLoC Samples',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: InitializationPage(),
),
);
}
}
2.2. 在子視圖樹(shù)(多個(gè)頁(yè)面或組件)中可用
大多數(shù)時(shí)候竟稳,我們只需要在應(yīng)用的部分頁(yè)面/組件樹(shù)中使用 BLoC属桦。舉個(gè)例子,在一個(gè) App 中有類(lèi)似論壇的功能模塊他爸,在這個(gè)功能模塊中我們需要用到 BLoC 來(lái)實(shí)現(xiàn):
- 與后端服務(wù)器交互聂宾,獲取、添加诊笤、更新帖子
- 在特定的頁(yè)面列出需要顯示的數(shù)據(jù)
- …
顯然我們不需要將論壇的 BLoC 實(shí)現(xiàn)成全局可用系谐,只需在涉及論壇的視圖樹(shù)中可用就行了。
那么可采用通過(guò) BlocProvider將 BLoC 作為模塊子樹(shù)的根(父級(jí))注入的方式讨跟,如下所示:
class MyTree extends StatelessWidget {
@override
Widget build(BuildContext context){
return BlocProvider<MyBloc>(
bloc: MyBloc(),
child: Column(
children: <Widget>[
MyChildWidget(),
],
),
);
}
}
class MyChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
MyBloc = BlocProvider.of<MyBloc>(context);
return Container();
}
}
這樣纪他,該模塊下所有 Widget 都可以通過(guò)調(diào)用 BlocProvider.of 來(lái)獲取 BLoC.
注意
上面給出的并不是最佳方案,因?yàn)槊看?MyTree 重構(gòu)(rebuild)時(shí)都會(huì)重新初始化 BLoC 晾匠,帶來(lái)的結(jié)果是:
- 丟失 BLoC 中已經(jīng)存在的數(shù)據(jù)內(nèi)容
- 重新初始化BLoC 要占用 CPU 時(shí)間
在這個(gè)例子中更好的方式是使用 StatefulWidget 茶袒,利用其持久化 State 的特性解決上述問(wèn)題,代碼如下:
class MyTree extends StatefulWidget {
@override
_MyTreeState createState() => _MyTreeState();
}
class _MyTreeState extends State<MyTree>{
MyBloc bloc;
@override
void initState(){
super.initState();
bloc = MyBloc();
}
@override
void dispose(){
bloc?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context){
return BlocProvider<MyBloc>(
bloc: bloc,
child: Column(
children: <Widget>[
MyChildWidget(),
],
),
);
}
}
這樣實(shí)現(xiàn)的話(huà)凉馆,即使 MyTree 組件重構(gòu)薪寓,也不會(huì)重新初始化 BLoC,而是直接使用之前的BLoC實(shí)例澜共。
2.3. 單一組件中可用
如果只在某一個(gè)組件(Widget)中使用 BLoC向叉,只需要在該組件內(nèi)構(gòu)建 BLoC 實(shí)例即可。