原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier Boelens 為 Reactive Programming - Streams - BLoC 寫的后續(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è)人覺得很有用的模式(至少我是這么認(rèn)為的)。這些模式為我節(jié)約了大量的開發(fā)時(shí)間,并且讓代碼更加易讀和調(diào)試。
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 查看。
1. BlocProvider 性能優(yōu)化
我想先給大家介紹下我結(jié)合 InheritedWidget 實(shí)現(xiàn) BlocProvider 的新方案辆床,這種方式相比原來(lái)基于 StatefulWidget 實(shí)現(xiàn)的方式有性能優(yōu)勢(shì)。
1.1. 舊的 BlocProvider 實(shí)現(xiàn)方案
之前我是基于一個(gè)常規(guī)的 StatefulWidget 來(lái)實(shí)現(xiàn) BlocProvider 的桅狠,代碼如下:
abstract class BlocBase {
void dispose();
}
// Generic BLoC provider
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
@required this.child,
@required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
@override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context){
return widget.child;
}
}
這種方案的優(yōu)點(diǎn)是:StatefulWidget 的 dispose() 方法可以確保在 BLoC 初始化時(shí)分配的內(nèi)存資源在不需要時(shí)可以釋放掉讼载。
譯者注
這個(gè)優(yōu)點(diǎn)是單獨(dú)基于 InheritedWidget 很難實(shí)現(xiàn)的轿秧,因?yàn)?InheritedWidget 沒有提供 dispose 方法,而 Dart 語(yǔ)言又沒有自帶的析構(gòu)函數(shù)
雖然這種方案運(yùn)行起來(lái)沒啥問題咨堤,但從性能角度卻不是最優(yōu)解菇篡。
這是因?yàn)?context.ancestorWidgetOfExactType() 是一個(gè)時(shí)間復(fù)雜度為 O(n) 的方法,為了獲取符合指定類型的 ancestor 吱型,它會(huì)沿著視圖樹從當(dāng)前 context 開始逐步往上遞歸查找其 parent 是否符合指定類型逸贾。如果當(dāng)前 context 和目標(biāo) ancestor 相距不遠(yuǎn)的話這種方式還可以接受,否則應(yīng)該盡量避免使用津滞。
下面是 Flutter 中定義這個(gè)方法的源碼:
@override
Widget ancestorWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
1.2. 新的 BlocProvider 實(shí)現(xiàn)方案
新方案雖然總體也是基于 StatefulWidget 實(shí)現(xiàn)的铝侵,但是組合了一個(gè) InheritedWidget
譯者注
即在原來(lái) StatefulWidget 的 child 外面再包了一個(gè) InheritedWidget
下面是實(shí)現(xiàn)的代碼:
Type _typeOf<T>() => T;
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
@required this.child,
@required this.bloc,
}): super(key: key);
final Widget child;
final T bloc;
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<_BlocProviderInherited<T>>();
_BlocProviderInherited<T> provider =
context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
return provider?.bloc;
}
}
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>>{
@override
void dispose(){
widget.bloc?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context){
return new _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
}
class _BlocProviderInherited<T> extends InheritedWidget {
_BlocProviderInherited({
Key key,
@required Widget child,
@required this.bloc,
}) : super(key: key, child: child);
final T bloc;
@override
bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}
新方案毫無(wú)疑問是具有性能優(yōu)勢(shì)的,因?yàn)橛昧?InheritedWidget触徐,在查找符合指定類型的 ancestor 時(shí)咪鲜,我們就可以調(diào)用 InheritedWidget 的實(shí)例方法 context.ancestorInheritedElementForWidgetOfExactType(),而這個(gè)方法的時(shí)間復(fù)雜度是 O(1)撞鹉,意味著幾乎可以立即查找到滿足條件的 ancestor疟丙。
Flutter 中該方法的定義源碼體現(xiàn)了這一點(diǎn):
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null
? null
: _inheritedWidgets[targetType];
return ancestor;
}
當(dāng)然這也是源于 Fluter Framework 緩存了所有 InheritedWidgets 才得以實(shí)現(xiàn)。
為什么要用 ancestorInheritedElementForWidgetOfExactType 而不用 inheritFromWidgetOfExactType ?
因?yàn)?inheritFromWidgetOfExactType 不僅查找獲取符合指定類型的Widget鸟雏,還將context 注冊(cè)到該Widget享郊,以便Widget發(fā)生變動(dòng)后,context可以獲取到新值孝鹊;
這并不是我們想要的炊琉,我們想要的僅僅就是符合指定類型的Widget(也就是 BlocProvider)而已。
1.3. 如何使用新的 BlocProvider 方案?
1.3.1. 注入 BLoC
Widget build(BuildContext context){
return BlocProvider<MyBloc>{
bloc: myBloc,
child: ...
}
}
1.3.2. 獲取 BLoC
Widget build(BuildContext context){
MyBloc myBloc = BlocProvider.of<MyBloc>(context);
...
}