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

原文:Reactive Programming - Streams - BLoC - Practical Use Cases 是作者 Didier Boelens 为 Reactive 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 extends StatefulWidget {
  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }): super(key: key);

  final T bloc;
  final Widget child;

  @override
  _BlocProviderState createState() => _BlocProviderState();

  static T of(BuildContext context){
    final type = _typeOf>();
    BlocProvider provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  }

  static Type _typeOf() => T;
}

class _BlocProviderState extends State>{
  @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;

abstract class BlocBase {
  void dispose();
}

class BlocProvider extends StatefulWidget {
  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }): super(key: key);

  final Widget child;
  final T bloc;

  @override
  _BlocProviderState createState() => _BlocProviderState();

  static T of(BuildContext context){
    final type = _typeOf<_BlocProviderInherited>();
    _BlocProviderInherited provider = 
            context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
    return provider?.bloc;
  }
}

class _BlocProviderState extends State>{
  @override
  void dispose(){
    widget.bloc?.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context){
    return new _BlocProviderInherited(
      bloc: widget.bloc,
      child: widget.child,
    );
  }
}

class _BlocProviderInherited 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{
        bloc: myBloc,
        child: ...
    }
}

1.3.2. 获取 BLoC

Widget build(BuildContext context){
    MyBloc myBloc = BlocProvider.of(context);
    ...
}

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