[譯]Flutter 響應(yīng)式編程:Steams 和 BLoC 實(shí)踐范例(1) - BlocProvider 性能優(yōu)化

原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier BoelensReactive Programming - Streams - BLoC 寫的后續(xù)

閱讀本文前建議先閱讀前篇,前篇中文翻譯有兩個(gè)版本:

  1. [譯]Flutter響應(yīng)式編程:Streams和BLoC by JarvanMo
    忠于原作的版本
  2. Flutter中如何利用StreamBuilder和BLoC來(lái)控制Widget狀態(tài) by 吉原拉面
    省略了一些初級(jí)概念扮授,補(bǔ)充了一些個(gè)人解讀

前言

在了解 BLoC, Reactive ProgrammingStreams 概念后痹仙,我又花了些時(shí)間繼續(xù)研究横蜒,現(xiàn)在非常高興能夠與你們分享一些我經(jīng)常使用并且個(gè)人覺得很有用的模式(至少我是這么認(rèn)為的)。這些模式為我節(jié)約了大量的開發(fā)時(shí)間,并且讓代碼更加易讀和調(diào)試。

  1. BlocProvider 性能優(yōu)化
    結(jié)合 StatefulWidgetInheritedWidget 兩者優(yōu)勢(shì)構(gòu)建 BlocProvider

  2. BLoC 的范圍和初始化
    根據(jù) BLoC 的使用范圍初始化 BLoC

  3. 事件與狀態(tài)管理
    基于事件(Event) 的狀態(tài) (State) 變更響應(yīng)

  4. 表單驗(yàn)證
    根據(jù)表單項(xiàng)驗(yàn)證來(lái)控制表單行為 (范例中包含了表單中常用的密碼和重復(fù)密碼比對(duì))

  5. 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 的桅狠,代碼如下:

bloc_provider_previous.dart

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)是:StatefulWidgetdispose() 方法可以確保在 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) StatefulWidgetchild 外面再包了一個(gè) InheritedWidget

下面是實(shí)現(xiàn)的代碼:

bloc_provider_new.dart

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);
    ...
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末又活,一起剝皮案震驚了整個(gè)濱河市苔咪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柳骄,老刑警劉巖团赏,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異耐薯,居然都是意外死亡舔清,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門曲初,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)体谒,“玉大人,你說我怎么就攤上這事复斥∮埽” “怎么了械媒?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵目锭,是天一觀的道長(zhǎng)评汰。 經(jīng)常有香客問我,道長(zhǎng)痢虹,這世上最難降的妖魔是什么被去? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮奖唯,結(jié)果婚禮上惨缆,老公的妹妹穿的比我還像新娘。我一直安慰自己丰捷,他們只是感情好坯墨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著病往,像睡著了一般捣染。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上停巷,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天耍攘,我揣著相機(jī)與錄音,去河邊找鬼畔勤。 笑死蕾各,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庆揪。 我是一名探鬼主播式曲,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嚷硫!你這毒婦竟也來(lái)了检访?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仔掸,失蹤者是張志新(化名)和其女友劉穎脆贵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體起暮,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卖氨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了负懦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筒捺。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纸厉,靈堂內(nèi)的尸體忽然破棺而出系吭,到底是詐尸還是另有隱情,我是刑警寧澤颗品,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布肯尺,位于F島的核電站沃缘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏则吟。R本人自食惡果不足惜槐臀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氓仲。 院中可真熱鬧水慨,春花似錦、人聲如沸敬扛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)啥箭。三九已至欢顷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捉蚤,已是汗流浹背抬驴。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缆巧,地道東北人布持。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像陕悬,于是被迫代替她去往敵國(guó)和親题暖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容