Flutter State Management状态管理全面分析

前言

2019 Google I/O 大会,google就推出Provider,成为官方推荐的状态管理方式之一,Flutter 状态管理一直是个很热门的话题,并且状态管理的库也是超级多,这确实是咱们每个作Flutter开发难以免的一道坎,既然这么重要,咱们如何去理解它,如何使用它,如何作到更好呢?接下来让我告诉你答案html

啰嗦几句

该文章已经经历了一周的迭代,预计还要一周左右,要作一个全面的分析,固然要每一个细节都要关注到,若是您以为好,请不要吝啬您的大拇指,顺便点个赞哦,么么哒,若是有不对的地方提出来,一个地方一个红包奖励哦,爱大家。react

主要内容

一张图告诉你,我要讲的主要内容。下面将围绕这八个方面来说。七个理论,一个实践。android

  • 状态管理是什么
  • 为何须要状态管理
  • 状态管理基本分类
  • 状态管理的底层逻辑
  • 状态管理的使用原则
  • 使用成熟状态管理库的弊端
  • 选择状态管理库的原则
  • Provider 深刻分析(学以至用)

状态管理是什么

咱们知道最基本的程序是什么:git

  • 程序=算法+数据结构

数据是程序的中心。数据结构和算法两个概念间的逻辑关系贯穿了整个程序世界,首先两者表现为不可分割的关系。其实Flutter不就是一个程序吗,那咱们面临的最底层的问题仍是算法和数据结构,因此咱们推导出github

  • Flutter=算法+数据结构

那状态管理是什么?我也用公式来表达一下,以下:算法

  • Flutter状态管理=算法+数据结构+UI绑定

瞬间秒懂有没有?来看一个代码例子:数据库

class ThemeBloc {
  final _themeStreamController = StreamController<AppTheme>();

  get changeTheTheme => _themeStreamController.sink.add;

  get darkThemeIsEnabled => _themeStreamController.stream;

  dispose() {
    _themeStreamController.close();
  }
}

final bloc = ThemeBloc();

class AppTheme {
  ThemeData themeData;

  AppTheme(this.themeData);
}
/// 绑定到UI
StreamBuilder<AppTheme>(
        initialData: AppTheme.LIGHT_THEME,
        stream: bloc.darkThemeIsEnabled,
        builder: (context, AsyncSnapshot<AppTheme> snapshot) {
          return MaterialApp(
            title: 'Jetpack',
            theme: snapshot.data.themeData,
            home: PageHome(),
            routes: <String, WidgetBuilder>{
              "/pageChatGroup": (context) => PageChatGroup(),
              "/LaoMeng": (context) => LaoMeng(),
            },
          );
        })
  • AppTheme 是数据结构
  • changeTheTheme 是算法
  • StreamBuilder 是绑定UI

这样一整套代码的逻辑就是咱们所说的Flutter状态管理,这样解释你们理解了吗?再细说,算法就是咱们如何管理,数据结构就是数据状态,状态管理的本质仍是如何经过合理的算法管理数据,如何取,如何接收等,最终展现在UI上,经过UI的变动来体现状态的管理逻辑。redux

为何须要

这里就须要明白一个事情,Flutter的不少优秀的设计都来源于React,对于react来讲,同级组件之间的通讯尤其麻烦,或者是很是麻烦了,因此咱们把全部须要多个组件使用的state拿出来,整合到顶部容器,进行分发。状态管理能够实现组件通讯、跨组件数据储存。推荐阅读对 React 状态管理的理解及方案对比,那么对于Flutter来讲呢?你知道Android、Ios等原生于Flutter最本质的区别吗?来看一段代码:api

//android
TextView tv = TextView()
tv.setText("text")
///flutter
setState{
    text = "text"
}

从上面代码咱们看出,Android的状态变动是经过具体的组件直接赋值,若是页面所有变动,你是否是须要每个都设置一遍呢?,而Flutter的变动就简单粗暴,setState搞定,它背后的逻辑是从新build整个页面,发现有变动,再将新的数据赋值,其实Android、Ios与flutter的本质的区别就是数据与视图彻底分离,固然Android也出现了UI绑定框架,彷佛跟React、Flutter愈来愈像,因此这也在另外一方面凸显出了,Flutter设计的先进性,没有什么创新,但更符合将来感,回过头来,仔细想想,这样设计有什么弊端?安全

对了你猜对了:页面如何刷新才是Flutter的关键,作Android的同窗确定也面临着一个问题,页面的重绘致使的丢帧问题,为了更好,咱们不少时候都选择了局部刷新来优化对吧,Android、Ios已经很明确的告诉UI要刷新什么更新什么,而对于Flutter来讲,这一点很不清晰,虽然Flutter也作了相似虚拟Dom优化重绘逻辑,但这些远远不够的,如何合理的更新UI才是最主要的,这个时候一大堆的状态管理就出来了,固然状态管理也不是仅仅为了解决更新问题。

我再抛出一个问题,若是我有一个widget A,我想在另一个widget B中改变widget A的一个状态,或者从网络、数据库取到数据,而后刷新它,怎么作?咱们来模拟一下,来看代码

糟糕的状态管理代码

class WidgetTest extends StatefulWidget {
  @override
  _WidgetTestState createState() => _WidgetTestState();
}

class _WidgetTestState extends State<WidgetTest> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          WidgetA(),
          WidgetB()
        ],
      ),
    );
  }
}

_WidgetAState _widgetAState;
class WidgetA extends StatefulWidget {
  @override
  _WidgetAState createState() {
    _widgetAState = _WidgetAState();
    return _widgetAState;
  }
}
class _WidgetAState extends State<WidgetA> {
  var title = "";
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(title),
    );
  }
}

class WidgetB extends StatefulWidget {
  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: RaisedButton(
        onPressed: () {
          _widgetAState.setState(() {
            _widgetAState.title = "WidgetB";
          });
        },
      ),
    );
  }
}

WidgetTest页面有两个widget,分别是WidgetA、WidgetB,WidgetB经过RaisedButton的onPressed来改变WidgetA的Text,怎么作到的呢,直接用WidgetA的_WidgetAState对象提供的setState函数来变动,没什么问题对吧,并且功能实现了,但你仔细思考一下,这有什么问题呢?

  • _WidgetAState 被全局化,并且它全部状态被暴漏出去,若是_WidgetAState有十个状态,只有一个想让别人变动,惋惜已经晚了, 你加'_'也不行,组件的隐私全没了
  • 耦合变高,WidgetB有_WidgetAState的强关联,咱们编码追求的解偶,在这里彻底被忽视了
  • 性能变差,为何这么说?由于每次_widgetAState.setState都会致使整个页面甚至子Widget的从新build,若是_widgetAState里面有成千上百的状态,性能确定差到极点
  • 不可测,程序变得难以测试

如何变好呢
这就须要选择一种合适的状态管理方式。

状态管理的目标
其实咱们作状态管理,不只仅是由于它的特色,而为了更好架构,不是吗?

  • 代码要井井有条,易维护,易阅读
  • 可扩展,易维护,能够动态替换UI而不影响算法逻辑
  • 安全可靠,保持数据的稳定伸缩
  • 性能佳,局部优化

这些不牢牢是状态管理的目的,也是咱们作一款优秀应用的基础架构哦。

基本分类

  • 局部管理 官方也称 Ephemeral state,意思是短暂的状态,这种状态根本不须要作全局处理

举个例子,以下方的_index,这就是一个局部或者短暂状态,只须要StatefulWidget处理便可完成

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}
  • 全局管理 官方称 App state,即应用状态,非短暂状态,您要在应用程序的许多部分之间共享,以及但愿在用户会话之间保持的状态,就是咱们所说的应用程序状态(有时也称为共享状态)

例如:

  • 用户偏好
  • 登陆信息
  • 购物车
  • 新闻阅读状态

状态分类官方定义

没有明确的通用规则来区分特定变量是短暂状态仍是应用程序状态。有时,您必须将一个重构为另外一个。例如,您将从一个明显的短暂状态开始,可是随着您的应用程序功能的增加,可能须要将其移至应用程序状态。 出于这个缘由,请使用下图进行分类:

总之,任何Flutter应用程序中都有两种概念性的状态类型。临时状态可使用State和setState()来实现,而且一般是单个窗口小部件的本地状态。剩下的就是您的应用状态。两种类型在任何Flutter应用程序中都有本身的位置,二者之间的划分取决于您本身的喜爱和应用程序的复杂性


没有最好的管理方式,只有最合适的管理方式

底层逻辑

底层逻辑我想告诉你的是,Flutter中目前有哪些能够作到状态管理,有什么缺点,适合作什么不适合作什么,只有你彻底明白底层逻辑,才不会畏惧复杂的逻辑,即便是复杂的逻辑,你也能选择合理的方式去管理状态。

  • State

StatefulWidget、StreamBuilder状态管理方式

  • InheritedWidget

专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发

  • Notification

与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但二者都在本身的Widget树中传递,没法跨越树传递。

  • Stream

数据流 如Bloc、flutter_redux、fish_redux等也都基于它来作实现
为何列这些东西?由于如今大部分流行的状态管理都离不开它们。理解它们比理解那些吹本身牛逼的框架要好的多。请关注底层逻辑,这样你才能游刃有余。下面咱们一个个分析一下:

State

State 是咱们经常使用并且使用最频繁的一个状态管理类,它必须结合StatefulWidget一块儿使用,StreamBuilder继承自StatefulWidget,一样是经过setState来管理状态

举个例子来看下:

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);

  @override
  _TapboxAState createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

引用官方的例子,这里_active状态就是经过State提供的setState函数来实现的
为何会让State去管理状态,而不是Widget自己呢?Flutter设计时让Widget自己是不变的,相似固定的配置信息,那么就须要一个角色来控制它,State就出现了,但State的任何更改都会强制整个Widget从新构建,固然你也能够覆盖必要方法本身控制逻辑。

再看个例子:

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

从这里你看出什么?对了,父组件能够经过setState来刷新子Widget的状态变化,因此得出以下观点
注意
setState是整个Widget从新构建(并且子Widget也会跟着销毁重建),这个点也是为何不推荐你大量使用StatefulWidget的缘由。若是页面足够复杂,就会致使严重的性能损耗。如何优化呢?建议使用StreamBuilder,它原理上也是State,但它作到了子Widget的局部刷新,不会致使整个页面的重建,是否是就好不少了呢?

State缺点

从上面的代码咱们分析一下它的缺点

  • 没法作到跨组件共享数据(这个跨是无关联的,若是是直接的父子关系,咱们不认为是跨组件)

setState是State的函数,通常咱们会将State的子类设置为私有,因此没法作到让别的组件调用State的setState函数来刷新

  • setState会成为维护的难点,由于啥哪哪都是。

随着页面状态的增多,你可能在调用setState的地方会愈来愈多,不能统一管理

  • 处理数据逻辑和视图混合在一块儿,违反代码设计原则

好比数据库的数据取出来setState到Ui上,这样编写代码,致使状态和UI耦合在一块儿,不利于测试,不利于复用。

State小结

固然反过来说,不是由于它有缺点咱们就不使用了,咱们追求的简单高效,简单实现,高效运行,当复杂到须要更好的管理的时候再重构。一个基本原则就是,状态是否须要跨组件使用,若是须要那就用别的办法管理状态而不是State管理。

InheritedWidget

InheritedWidget是一个无私的Widget,它能够把本身的状态数据,无私的交给全部的子Widget,全部的子Widget能够无条件的继承它的状态。就这么一个东西。有了State咱们为何还须要它呢?咱们已经知道,State是能够更新直接子Widget的状态,但若是是子Widget的子Widget呢,因此说InheritedWidget的存在,一是为了更简单的获取状态,二是你们都共享这个状态,举个例子

class InheritedWidgetDemo extends InheritedWidget {

  final int accountId;

  InheritedWidgetDemo(this.accountId, {Key key, Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidgetDemo old) =>
      accountId != old.accountId;

  static InheritedWidgetDemo of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedWidgetDemo>();
  }

}

class MyPage extends StatelessWidget {
  final int accountId;

  MyPage(this.accountId);

  Widget build(BuildContext context) {
    return new InheritedWidgetDemo(
      accountId,
      child: const MyWidget(),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget();

  Widget build(BuildContext context) {
    return MyOtherWidget();
  }
}

class MyOtherWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final myInheritedWidget = InheritedWidgetDemo.of(context);
    print(myInheritedWidget.accountId);
  }

}
  • InheritedWidgetDemo共享状态accountId给了MyOtherWidget,而MyOtherWidget是MyWidget的子Widget,这就是InheritedWidget的功效,它能够作到跨组件共享状态。
  • const MyWidget() 表示该Widget是常量,不会由于页面的刷新致使从新build,这就是优化的细节,这里想一下,若是你用State实现,不是就须要它setState才能实现MyOtherWidget的从新build,这样作的坏处就是致使整个UI的刷新。
  • updateShouldNotify 它也是一个优化点,在你横屏变竖屏的同时,致使整个UI从新build,可因为updateShouldNotify的判断,系统将不会从新build MyOtherWidget,也是一种布局优化。
  • 子树中的组件经过InheritedWidgetDemo.of(context)访问共享状态。

有的人想了,InheritedWidget这么好用,那我把整个App的状态都存进来怎么样?相似这样

class AppContext {
  int teamId;
  String teamName;
  
  int studentId;
  String studentName;
  
  int classId;
  ...
}

其实这样很差,咱们不光是要作技术上的组件化,更要关注的是业务,对业务的充分理解并实现模块化分工,在使用InheritedWidget时候特别是要注意这一点,更推荐你使用该方案:

class TeamContext {
  int teamId;
  String teamName;
}

class StudentContext {
  int studentId;
  String studentName;
}
 
class ClassContext {
  int classId;
  ...
}

注意
它的数据是只读的,虽然很无私,但子widget不能修改,那么如何修改呢?
举个例子:

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}

该例子引用自widget-state-context-inheritedwidget/欢迎阅读学习哦

  • _MyInherited是InheritedWidget,每次咱们经过单击“ Widget A”的按钮添加元素时都会从新建立
  • MyInheritedWidget是一个状态为包含元素列表的窗口小部件。可经过(BuildContext上下文)的静态MyInheritedWidgetState访问此状态
  • MyInheritedWidgetState公开一个getter(itemsCount)和一个方法(addItem),以便子控件树的一部分的控件可使用它们
  • 每次咱们向State添加元素时,都会从新构建MyInheritedWidgetState
  • MyTree类仅构建一个小部件树,将MyInheritedWidget做为该树的父级
  • WidgetA是一个简单的RaisedButton,按下该按钮时,会调用最近的MyInheritedWidget的addItem方法。
  • WidgetB是一个简单的Text,它显示在最接近的 MyInheritedWidget级别上显示的元素数量

看了一下日志输出如图:

有没有发现一个问题?当MyInheritedWidgetState.addItem,致使setState被调用,而后就触发了WidgetA、WidgetB的build的方法,而WidgetA根本不须要从新build,这不是浪费吗?那么咱们如何优化呢?

static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
                    : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }

经过抽象rebuild属性来控制是否须要从新build

final MyInheritedWidgetState state = MyInheritedWidget.of(context,false);

而后用的时候加以参数控制,改完代码,再看下日志:

看,已经生效了。你如今是否是对InheritedWidget有了更清晰的认识了呢?但说到它就不得不提InheritedModel,它是InheritedWidget的子类,InheritedModel能够作到部分数据改变的时候才会重建,你能够修改上面例子

class _MyInheritedWidget extends InheritedModel {

  static MyInheritedWidgetState of(BuildContext context, String aspect) {
     return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
   }

   @override
   bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) {
     return aspects.contains('true');
   }
 }

调用修改成:

///不容许从新build
 final MyInheritedWidgetState state = MyInheritedWidget.of(context,"false");
///容许从新build
final MyInheritedWidgetState state = MyInheritedWidget.of(context,"true");

推荐阅读

inheritedmodel-vs-inheritedwidget

https://juju.one/inheritedwidget-inheritedmodel/

widget-state-context-inheritedwidget/

InheritedWidget 缺点

经过上面的分析,咱们来看下它的缺点

  • 容易形成没必要要的刷新
  • 不支持跨页面(route)的状态,意思是跨树,若是不在一个树中,咱们没法获取
  • 数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用

InheritedWidget 小结

通过一系列的举例和验证,你也基本的掌握了InheritedWidget了吧,这个组件特别适合在同一树型Widget中,抽象出公有状态,每个子Widget或者孙Widget均可以获取该状态,咱们还能够经过手段控制rebuild的粒度来优化重绘逻辑,但它更适合从上往下传递,若是是从下往上传递,咱们如何作到呢?请往下看,立刻给你解答

Notification

它是Flutter中跨层数据共享的一种机制,注意,它不是widget,它提供了dispatch方法,来让咱们沿着context对应的Element节点向上逐层发送通知

具个简单例子看下

class TestNotification extends Notification {
  final int test;

  TestNotification(this.test);
}

var a = 0;

// ignore: must_be_immutable
class WidgetNotification extends StatelessWidget {

  final String btnText;

  WidgetNotification({Key key, this.btnText}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: RaisedButton(
        child: Text(btnText),
        onPressed: () {
          var b = ++a;
          debugPrint(b.toString());
          TestNotification(b).dispatch(context);
        },
      ),
    );
  }
}

class WidgetListener extends StatefulWidget {
  @override
  _WidgetListenerState createState() => _WidgetListenerState();
}

class _WidgetListenerState extends State<WidgetListener> {
  int _test = 1;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          NotificationListener<TestNotification>(
            child: Column(
              children: <Widget>[
                Text("监听$_test"),
                WidgetNotification(btnText: "子Widget",)
              ],
            ),
            onNotification: (TestNotification notification) {
              setState(() {
                _test = notification.test;
              });
              return true;
            },
          ),
          WidgetNotification(btnText: "非子Widget",)
        ],
      ),
    );
  }
}
  • 定义TestNotification通知的实现
  • WidgetNotification 负责通知结果,经过RaisedButton的点击事件,将数据a传递出去,经过Notification提供的dispatch方法向上传递
  • WidgetListener经过Widget NotificationListener来监听数据变化,最终经过setState变动数据
  • WidgetNotification 实例化了两次,一次在NotificationListener的树内部,一个在NotificationListener的外部,通过测试发现,在外部的WidgetNotification并不能通知到内容变化。

因此说在使用Notification的时候要注意,若是遇到没法收到通知的状况,考虑是不是Notification 未在NotificationListener的内部发出通知,这个必定要注意。

一样的思路,我想看下Notification是如何刷新Ui的
在代码里加入了跟通知可有可无的WidgetC


这么看来,你觉得是Notification致使的吗?我把这个注释掉,如图

再运行看下,连续点击了八次

原来是State的缘由,那么这种状况咱们如何优化呢?这就用到了Stream了,请接着往下继续看哦。

推荐阅读

flutter-notifications-bubble-up-and-values-go-down

notification

Notification缺点

  • 不支持跨页面(route)的状态,准备的说不支持NotificationListener同级或者父级Widget的状态通知
  • 自己不支持刷新UI,须要结合State使用
  • 若是结合State,会致使整个UI的重绘,效率底下不科学

Notification小结

使用起来很简单,但在刷新UI方面须要注意,若是页面复杂度很高,致使可有可无的组件跟着刷新,得不偿失,还须要另找蹊径,躲开这些坑,下面我来介绍如何完美躲闪,重磅来袭Stream。

Stream

它实际上是纯Dart的实现,跟Flutter没什么关系,扯上关系的就是用StreamBuilder来构建一个Stream通道的Widget,像知名的rxdart、BloC、flutter_redux全都用到了Stream的api。因此学习它才是咱们掌握状态管理的一个关键

推荐阅读
我本身写的StreamBuilder源码分析
大神写的Stream全面分析

Stream 缺点

  • api生涩,很差理解
  • 须要定制化,才能知足更复杂的场景

缺点偏偏是它的优势,保证了足够灵活,你更可基于它作一个好的设计,知足当下业务的设计。

小结

经过对State、InheritedWidget、Notification、Stream的学习,你是否是以为,Flutter的状态管理也就这些了呢?不必定哈,我也在不断的学习,若是碰到新的技术,继续分享给大家哦。难道这就完了吗?没有,其实咱们只是学了第一步,是什么,如何用,尚未讨论怎么用好呢?须要什么标准吗,固然有,下面经过个人项目实战经验来提出一个基本原则,超过这个原则你就是在破坏平衡,请往下看。

状态管理的使用原则

局部管理优于全局

这个原则来源于,Flutter的性能优化,局部刷新确定比全局刷新要好不少,那么咱们在管理状态的同时,也要考虑该状态究竟是局部仍是全局,从而编写正确的逻辑。

保持数据安全性

用“_”私有化状态,由于当开发人员众多,当别人看到你的变量的时候,第一反应可能不是找你提供的方法,而是直接对变量操做,那就有可能出现想不到的后果,若是他只能调用你提供的方法,那他就要遵循你方法的逻辑,避免数据被处理错误。

考虑页面从新build带来的影响

不少时候页面的重建都会调用build函数,也就是说,在一个生命周期内,build函数是屡次被调用的,因此你就要考虑数据的初始化或者刷新怎么样才能合理。

使用成熟状态管理库弊端

  • 增长代码复杂性
  • 框架bug修复须要时间等待
  • 不理解框架原理致使使用方式不对,反而带来更多问题
  • 选型错误致使不符合应用要求
  • 与团队风格冲突不适用

经过了解它们的弊端来规避一些风险,综合考虑,选框架不易,且行且珍惜。

选型原则

  • 侵入性
  • 扩展性
  • 高性能
  • 安全性
  • 驾驭性
  • 易用性
  • 范围性

全部的框架都有侵入性,你赞成吗?不一样意请左转,前面有个坑,你能够跳过去。目前侵入性比较高的表明ScopedModel,为啥?由于它是用extend实现的,须要继承实现的基本不是什么好实现,你赞成吗?同上。
扩展性就不用说了,若是你选择的框架只能使用它提供的几个入口,那么请你放弃使用它。高性能也是很重要的,这个须要明白它的原理,看它到底如何作的管理。安全性也很重要,看他数据管理通道是否安全稳定。驾驭性,你说你都不理解你就敢用,出了问题找谁?若是驾驭不了也不要用。易用性你们应该都明白,若是用它一个框架须要N多配置,N多实现,放弃吧,不合适。简单才是硬道理。

范围性
这个特色是flutter中比较明显的,框架选型必定要考虑框架的适用范围,究竟是适合作局部管理,仍是适合全局管理,要作一个实际的考量。

推荐用法

若是是初期,建议多使用Stream、State、Notification来自行处理,顺便学习源码,多理解,多实践。有架构能力的就能够着手封装了,提供更简单的使用方式
若是是后期,固然也是在前面的基础之上,再去考虑使用Provider、redux等复杂的框架,原则上要吃透源码,不然不建议使用。

注意

你觉得使用框架就能万事大吉了?性能优化是一个不变的话题,包括Provider在内的,若是你使用不当,照样出现页面的性能损耗严重,因此你又回到了为啥会这样,请你学习上面的底层逻辑,谢谢🙏

总结

经过这期分享,你是否是对Flutter的状态管理有了一个从新的认识呢?若是对你有帮住,请点一下下面的赞哦。谢谢🙏。

相关文章
相关标签/搜索