[译]Flutter 响应式编程:Steams 和 BLoC 实践范例(1) - BlocProvider 性能优化

原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier BoelensReactive Programming - Streams - BLoC 写的后续

阅读本文前建议先阅读前篇,前篇中文翻译有两个版本:

  1. [译]Flutter响应式编程:Streams和BLoC by JarvanMo
    忠于原作的版本
  2. Flutter中如何利用StreamBuilder和BLoC来控制Widget状态 by 吉原拉面
    省略了一些初级概念,补充了一些个人解读

前言

在了解 BLoC, Reactive ProgrammingStreams 概念后,我又花了些时间继续研究,现在非常高兴能够与你们分享一些我经常使用并且个人觉得很有用的模式(至少我是这么认为的)。这些模式为我节约了大量的开发时间,并且让代码更加易读和调试。

  1. BlocProvider 性能优化
    结合 StatefulWidgetInheritedWidget 两者优势构建 BlocProvider

  2. BLoC 的范围和初始化
    根据 BLoC 的使用范围初始化 BLoC

  3. 事件与状态管理
    基于事件(Event) 的状态 (State) 变更响应

  4. 表单验证
    根据表单项验证来控制表单行为 (范例中包含了表单中常用的密码和重复密码比对)

  5. Part Of 模式
    允许组件根据所处环境(是否在某个列表/集合/组件中)调整自身的行为

文中涉及的完整代码可在 GitHub 查看。

1. BlocProvider 性能优化

我想先给大家介绍下我结合 InheritedWidget 实现 BlocProvider 的新方案,这种方式相比原来基于 StatefulWidget 实现的方式有性能优势。

1.1. 旧的 BlocProvider 实现方案

之前我是基于一个常规的 StatefulWidget 来实现 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;
  }
}

这种方案的优点是:StatefulWidgetdispose() 方法可以确保在 BLoC 初始化时分配的内存资源在不需要时可以释放掉。

译者注

这个优点是单独基于 InheritedWidget 很难实现的,因为 InheritedWidget 没有提供 dispose 方法,而 Dart 语言又没有自带的析构函数

虽然这种方案运行起来没啥问题,但从性能角度却不是最优解。

这是因为 context.ancestorWidgetOfExactType() 是一个时间复杂度为 O(n) 的方法,为了获取符合指定类型的 ancestor ,它会沿着视图树从当前 context 开始逐步往上递归查找其 parent 是否符合指定类型。如果当前 context 和目标 ancestor 相距不远的话这种方式还可以接受,否则应该尽量避免使用。

下面是 Flutter 中定义这个方法的源码:

@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 实现方案

新方案虽然总体也是基于 StatefulWidget 实现的,但是组合了一个 InheritedWidget

译者注

即在原来 StatefulWidgetchild 外面再包了一个 InheritedWidget

下面是实现的代码:

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;
}

新方案毫无疑问是具有性能优势的,因为用了 InheritedWidget,在查找符合指定类型的 ancestor 时,我们就可以调用 InheritedWidget 的实例方法 context.ancestorInheritedElementForWidgetOfExactType(),而这个方法的时间复杂度是 O(1),意味着几乎可以立即查找到满足条件的 ancestor

Flutter 中该方法的定义源码体现了这一点:

@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null 
                                    ? null 
                                    : _inheritedWidgets[targetType];
    return ancestor;
}

当然这也是源于 Fluter Framework 缓存了所有 InheritedWidgets 才得以实现。

为什么要用 ancestorInheritedElementForWidgetOfExactType 而不用 inheritFromWidgetOfExactType ?

因为 inheritFromWidgetOfExactType 不仅查找获取符合指定类型的Widget,还将context 注册到该Widget,以便Widget发生变动后,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);
    ...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容