Flutter实践:深入 flutter 的状态管理方式(2)——演化BloC

栏目: IOS · Android · 发布时间: 5年前

内容简介:在上篇文章中,我详细介绍了 InheritedWidget 及 ScopedModel 实现原理与方法,有同学说找不到源码,其实上篇文章包括这篇文章里的源码都按步骤放在这篇的重点我将放在 BloC 的实现上面,我们已经知道 Strems 的概念,RXDart 是依赖 Streams 使用的输入(我们先来实践一下如何在 widget 中使用数据。Flutter 提供了一个名为StreamBuilder 的 StatefulWidget。

在上篇文章中,我详细介绍了 InheritedWidget 及 ScopedModel 实现原理与方法,有同学说找不到源码,其实上篇文章包括这篇文章里的源码都按步骤放在 样例代码 里了,有同学说有点懵,其实上一篇的概念过多而且本身我表达也不是很清晰,英文文档中我也解释的没有完全语义化,所以还请谅解, 结合代码你会有更好地理解

这篇的重点我将放在 BloC 的实现上面,我们已经知道 Strems 的概念,RXDart 是依赖 Streams 使用的输入( Sink )和输出( Stream )封装而成的响应式库,BloC 基于此便可以实时侦听数据的变化而改变数据,并且,BloC 主要解决的问题就是他不会一刀切的更新整个状态树,它关注的是数据,经过一系列处理后得到它并且只改变应用它的 widget。

Flutter实践:深入 flutter 的状态管理方式(2)——演化BloC

如何将 Stream 中的数据应用到 Widget?

我们先来实践一下如何在 widget 中使用数据。Flutter 提供了一个名为StreamBuilder 的 StatefulWidget。

StreamBuilder 监听 Stream,每当一些数据流出 Stream 时,它会自动重建,调用其构建器回调。

StreamBuilder<T>(
    key: ...optional, the unique ID of this Widget...
    stream: ...the stream to listen to...
    initialData: ...any initial data, in case the stream would initially be empty...
    builder: (BuildContext context, AsyncSnapshot<T> snapshot){
        if (snapshot.hasData){
            return ...the Widget to be built based on snapshot.data
        }
        return ...the Widget to be built if no data is available
    },
)
复制代码

以下示例使用 Stream 而不是 setState() 模拟默认的“计数器”应用程序:

import 'dart:async';
import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;
  final StreamController<int> _streamController = StreamController<int>();

  @override
  void dispose(){
    _streamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream version of the Counter App')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _streamController.stream,
          initialData: _counter,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot){
            return Text('You hit me: ${snapshot.data} times');
          }
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          _streamController.sink.add(++_counter);
        },
      ),
    );
  }
}
复制代码
  • 第24-30行:我们监听流,每次有一个新值流出这个流时,我们用该值更新 Text;
  • 第35行:当我们点击 FloatingActionButton 时,我们递增计数器并通过接收器将其发送到 Stream; 侦听它的 StreamBuilder 注入了该值相应到后重建并“刷新”计数器;
  • 我们不再需要 State,所有东西都可以通过 Stream 接受;
  • 这里实现了相当大的优化,因为调用 setState() 方法会强制整个 Widget(和任何子组件)重新渲染。 而在这里,只重建 StreamBuilder(当然还有其子组件);
  • 我们仍需要使用 StatefulWidget 的唯一原因,仅仅是因为我们需要通过 dispose 方法第15行释放StreamController;

实现真正的 BloC

是时候展现真正的计技术了,我们依然将 BloC 用于默认的计数器应用中:

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        title: 'Streams Demo',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: BlocProvider<IncrementBloc>(
          bloc: IncrementBloc(),
          child: CounterPage(),
        ),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Stream version of the Counter App')),
      body: Center(
        child: StreamBuilder<int>(
          stream: bloc.outCounter,
          initialData: 0,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot){
            return Text('You hit me: ${snapshot.data} times');
          }
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          bloc.incrementCounter.add(null);
        },
      ),
    );
  }
}

class IncrementBloc implements BlocBase {
  int _counter;

  //
  // Stream to handle the counter
  //
  StreamController<int> _counterController = StreamController<int>();
  StreamSink<int> get _inAdd => _counterController.sink;
  Stream<int> get outCounter => _counterController.stream;

  //
  // Stream to handle the action on the counter
  //
  StreamController _actionController = StreamController();
  StreamSink get incrementCounter => _actionController.sink;

  //
  // Constructor
  //
  IncrementBloc(){
    _counter = 0;
    _actionController.stream
                     .listen(_handleLogic);
  }

  void dispose(){
    _actionController.close();
    _counterController.close();
  }

  void _handleLogic(data){
    _counter = _counter + 1;
    _inAdd.add(_counter);
  }
}
复制代码

这是上篇文章的最后给打大家制造悬念的代码?五脏俱全,基本已经实现了 BloC。

结合上面的例子来分析 BloC 体现出来的优势:(建议先将 这段代码 跑起来!)

一,BloC 实现了责任分离

你可以看到 CounterPage(第21-45行),其中没有任何业务逻辑。

它承担的负责仅有:

  • 显示计数器,现在只在必要时更新
  • 提供一个按钮,当按下时,请求执行动作

此外,整个业务逻辑集中在一个单独的类“IncrementBloc”中。

如果现在,如果我们需要更改业务逻辑,只需更新方法 _handleLogic(第77-80行)。 也许新的业务逻辑将要求做非常复杂的事情...... CounterPage 永远与它无关!

二,可测试性

现在,测试业务逻辑也变得更加容易。

无需再通过用户界面测试业务逻辑。 只需要测试 IncrementBloc 类。

三,任意组织布局

由于使用了 Streams,您现在可以独立于业务逻辑组织布局。

你可以从应用程序中的任何位置用任何操作:只需调用 .incrementCounter 接收器即可。

您可以在任何页面的任何位置显示计数器,只需舰艇监听 .outCounter 流。

四,减少 “build” 的数量

不用 setState() 而是使用 StreamBuilder,从而大大减少了“构建”的数量,只减少了所需的数量。

这是性能上的巨提高!

只有一个约束...... BLoC的可访问性

为了达到各种目的,BLoC 需要可访问。

有以下几种方法可以访问它:

  • 通过全局单例的变量

    这种方式很容易实现,但不推荐。 此外,由于 Dart 中没有类析构函数,因此我们永远无法正确释放资源。

  • 作为本地实例

    您可以实例化 BLoC 的本地实例。 在某些情况下,此解决方案完全符合需求。 在这种情况下,您应该始终考虑在 StatefulWidget 中初始化,以便您可以利用 dispose() 方法来释放它。

  • 由根组件提供 使其可访问的最常见方式是通过根 Widget,将其实现为 StatefulWidget。

    以下代码给出了一个通用 BlocProvider 的示例:(这个例子牛逼!)

    // Generic Interface for all BLoCs
    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;
      }
    }
    复制代码

    关于这段通用的 BlocProvider 仔细回味,你会发现其精妙之处!

    通用 BlocProvider 的一些解释:

    首先,如何将其用作数据提供者?

    如果你看了上面 BloC 计数器的示例代码 示例代码,您将看到以下代码行(第12-15行)

    home: BlocProvider<IncrementBloc>(
              bloc: IncrementBloc(),
              child: CounterPage(),
            ),
    复制代码

    使用以上代码,我们实例化了一个想要处理 IncrementBloc 的新 BlocProvider ,并将 CounterPage 呈现为子组件。

    BlocProvider 开始的子组件的任何组件部分都将能够通过以下行访问 IncrementBloc

    IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);
    复制代码

BLoC 的基本使用就介绍完了,所有 实例代码在这里 ,我将每种状态管理的方法分模块放在里面,选择使用哪种方式运行代码即可。

BloC 其他你必须知道的事情

可以实现多个 BloC

在大型项目中,这是非常可取的。 给以下几个建议:

  • (如果有任何业务逻辑)每页顶部有一个BLoC,
  • 用一个 ApplicationBloc 来处理应用程序所有状态
  • 每个“足够复杂的组件”都有相应的BLoC。

以下示例代码在整个应用程序的顶部使用 ApplicationBloc ,然后在 CounterPage 顶部使用 IncrementBloc 。该示例还展示了如何使用两个 Bloc:

void main() => runApp(
  BlocProvider<ApplicationBloc>(
    bloc: ApplicationBloc(),
    child: MyApp(),
  )
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      title: 'Streams Demo',
      home: BlocProvider<IncrementBloc>(
        bloc: IncrementBloc(),
        child: CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context){
    final IncrementBloc counterBloc = BlocProvider.of<IncrementBloc>(context);
    final ApplicationBloc appBloc = BlocProvider.of<ApplicationBloc>(context);
    
    ...
  }
}
复制代码

为何不用 InheritedWidget 来全局管理 BloC 的状态

我为此也整理了一个将 BLoC 结合 InheritedWidget 使用的示例: bloc_inherited (在 Vscode 打开这段代码是 [close_sinks] 的警告的)

在很多与 BLoC 相关的文章中,您将看到 Provider 的实现其实是一个 InheritedWidget

当然, 这是完全可以实现的,然而,

  • 一个 InheritedWidget 没有提供任何 dispose 方法,记住,在不再需要资源时总是释放资源是一个很好的做法。
  • 当然,你也可以将 InheritedWidget 包装在另一个 StatefulWidget 中,但是,乍样使用 InheritedWidget 并没有什么便利之处!
  • 最后,如果不受控制,使用 InheritedWidget 经常会导致一些副作用(请参阅下面的 InheritedWidget 上的提醒)。

这 3 点解释了我为何将通用 BlocProvider 实现为 StatefulWidget,这样我就可以 释放资源

Flutter无法实例化泛型类型

不幸的是,Flutter 无法实例化泛型类型,我们必须将 BLoC 的实例传递给 BlocProvider。 为了在每个BLoC中强制执行 dispose() 方法,所有BLoC都必须实现 BlocBase 接口。

关于使用 InheritedWidget 的提醒

在使用 InheritedWidget 并通过 context.inheritFromWidgetOfExactType(...) 获取指定类型最近的 Widget 时,每当InheritedWidget 的父级或者子布局发生变化时,这个方法会自动将当前 “ context ”(= BuildContext )注册到要重建的 widget 当中。

请注意,为了完全正确,我刚才解释的与 InheritedWidget 相关的问题只发生在我们将 InheritedWidgetStatefulWidget 结合使用时。 当您只使用没有 State 的 InheritedWidget 时,问题就不会发生。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

操作系统

操作系统

[美] William Stallings / 陈向群、陈渝 / 电子工业出版社 / 2012-9 / 75.00元

《国外计算机科学教材系列•操作系统:精髓与设计原理(第7版)》是一本关于操作系统的概念、结构和机制的教材,其目的是尽可能清楚和全面地展示现代操作系统的本质和特点;同时,《国外计算机科学教材系列•操作系统:精髓与设计原理(第7版)》也是讲解操作系统的经典教材,不仅系统地讲述了操作系统的基本概念、原理和方法,而且以当代最流行的操作系统——Windows 7、UNIX和Linux为例,全面清楚地展现了当......一起来看看 《操作系统》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具