⚠️Flutter的 状态管理⚠️

👏欢迎前往本人的GitHub查看更多内容。点击前往GitHub

1 对于状态管理的理解

最近项目开发完成后,没有什么任务安排,就自己对项目进行了查缺补漏。如果说flutter开发中最不可避免的话,肯定是状态管理啦。我们自己的项目当中用的是Provider来进行管理的,自己其实对于状态管理的基础还是有一点模糊,粗浅的理解就是:避免对页面不停的build,要进行针对性的刷新,(列入:更新一个text也将整个页面build一次浪费性能。)




1.0 简单理解

那我们来言归正传说一说比较正确的理解吧,避免我在这里误人子弟。

1.状态管理的目的就是为了让界面与业务分离。
2.当我们的应用功能复杂多样的时候,应用程序将会有几十个甚至上百个状态,这时候我们需要对状态进行合理有效的管理。

很多从命令式编程框架(Android或iOS原生开发者)转成声明式编程(Flutter、Vue、React等)刚开始并不适应,因为需要一个新的角度来考虑APP的开发模式。Flutter作为一个现代的框架,是声明式编程的:

image

在编写一个应用的过程中,我们有大量的状态需要来进行管理,而正是对这些State的改变,来更新界面的刷新:
image

1.1 InheritedWidget(共享应用程序状态)

Flutter 中Widget 多种多样,有UI的,当然也有功能型的组件InheritedWidget 组件就是Flutter 中的一个功能组件,它可以实现Flutter 组件之间的数据共享,他的数据传递方向在Widget树传递是从上到下的。

使用样列:
class StateManagementDemo extends StatefulWidget {
  @override
  _StateManagementDemoState createState() => _StateManagementDemoState();
}

class _StateManagementDemoState extends State<StateManagementDemo> {
  int _count = 0;
  void _increaseCount () {
    setState(() {
      _count += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CounterProvider(
      count: _count,
      increaseCount: _increaseCount,
      child: Scaffold(
        appBar: AppBar(title: Text('StateManagementDemo'), elevation: 0.0,),
        body: CounterWrapper(),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: _increaseCount,
        ),
      ),
    );
  }
}

class CounterWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Counter(),);
  }
}

class Counter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final int count = CounterProvider.of(context).count;
    final VoidCallback increaseCount = CounterProvider.of(context).increaseCount;
    return Center(child: ActionChip(
      label: Text('$count'),
      onPressed: increaseCount,),
    );
  }
}

class CounterProvider extends InheritedWidget {
  final int count;
  final VoidCallback increaseCount;
  final Widget child;

  CounterProvider({
    this.count,
    this.increaseCount,
    this.child,
  }) : super(child: child);
  // 其他部件中可以用这个方法得到小部件内的数据
  static CounterProvider of(BuildContext context) => context.inheritFromWidgetOfExactType(CounterProvider);
  // 决定是否通知继承这个小部件的小部件,当这个部件重建以后有时候需要通知继承这个部件的小部件
  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}






2 状态管理框架

2.0 可以通过如下方式来进行状态管理

1.setState :
最重要的方式 setState,支持规模较小的程序足够了,所有其它方式最终都需要调用 setState。

2.Function callback
Dart Function 足够灵活,还支持模版参数。
typedef FooChanged = void Function(int);
typedef ValueChanged<T> = void Function(T value);
单向变更通知,可以和ObserverList结合支持多个订阅者。
Flutter 内置 ChangeNotifier, ValueNotifier 都可以认为是类似方案。

3.Delegate
可以认为是多个回调函数,其他语言里都有类似模式,名称似乎来源于 Objective-C。实际例子
abstract class SpiderDelegate {
  /// category is null, crawl book whole site
  /// category not null, crawl book under the category
  void onBook(Book book, {Site site, Category category});

  void onChapter(Book book, Chapter chapter);
}


4.Sigslot
源自 Qt 里的经典编程模式,Dart 可以轻易实现。这种方式在 Flutter 里可能根本不会有太多应用,但是由于 Sigslot 在 C++ 领域具有举足轻重的地位,属于界面数据和逻辑解耦合的王者,boost::signal(2)就是明证,在这里列出纯属凑数(本人也实现了一个)。
typedef ValueCallback<E> = void Function(E value);
abstract class Signable<E> {
  // Signable<bool> someValue;
  /// Register a closure to be called when the object notifies its listeners.
  void connect(ValueCallback<E> listener);

  /// Remove a previously registered closure from the list of closures that the
  /// object notifies.
  void disconnect(ValueCallback<E> listener);

  /// sink value changed
  void emit(E value);
}

使用方法类似
Signal<String> signalString;
signalString.connect((String str) {
  // got the `str` changed here
});




2.1 scoped_model

Scoped_model是一个dart第三方库,提供了让您能够轻松地将数据模型从父Widget传递到它的后代的功能。此外,它还会在模型更新时重新渲染使用该模型的所有子项。
它直接来自于Google正在开发的新系统Fuchsia核心Widgets 中对Model类的简单提取,作为独立使用的独立Flutter插件发布。

Scoped model使用了观察者模式,将数据模型放在父代,后代通过找到父代的model进行数据渲染,最后数据改变时将数据传回,父代再通知所有用到了该model的子代去更新状态。




2.2 Redux

Redux是一种单向数据流架构,可以轻松开发,维护和测试应用程序。
image
我们在Redux中,所有的状态都储存在Store里。这个Store会放在App顶层。
View拿到Store储存的状态(State)并把它映射成视图。View还会与用户进行交互,用户点击按钮滑动屏幕等等,这时会因为交互需要数据发生改变。
Redux让我们不能让View直接操作数据,而是通过发起一个action来告诉Reducer,状态得改变啦。
这时候Reducer接收到了这个action,他就回去遍历action表,然后找到那个匹配的action,根据action生成新的状态并把新的状态放到Store中。
Store丢弃了老的状态对象,储存了新的状态对象后,就通知所有使用到了这个状态的View更新(类似setState)。这样我们就能够同步不同view中的状态了。




2.3 BLoC

BLoC是一种利用reactive programming方式构建应用的方法,这是一个由流构成的完全异步的世界。
image
* 用StreamBuilder包裹有状态的部件,streambuilder将会监听一个流
* 这个流来自于BLoC
* 有状态小部件中的数据来自于监听的流。
* 用户交互手势被检测到,产生了事件。例如按了一下按钮。
* 调用bloc的功能来处理这个事件
* 在bloc中处理完毕后将会吧最新的数据add进流的sink中
* StreamBuilder监听到新的数据,产生一个新的snapshot,并重新调用build方法
* Widget被重新构建




2.4 RxDart

RxDart是基于ReactiveX标准API的Dart版本实现,由Dart标准库中Stream扩展而成。因此,RxDart与Dart的相关术语稍有区别:

Dart    RxDart
StreamController    Subject
Stream  Observable
Observable等同于Stream,Subject等同于StreamController,前者均由后者继承而来。
不同于Dart,RxDart提供了三种StreamController的变体来应用到不同的场景:

PublishSubject
BehaviorSubject
ReplaySubject




2.5 Fish-Redux【推荐👍】

Fish Redux 是一个基于 Redux 数据管理的组装式 flutter 应用框架, 它特别适用于构建中大型的复杂应用。它的特点是配置式组装。
一方面我们将一个大的页面,对视图和数据层层拆解为互相独立的 Component|Adapter,上层负责组装,下层负责实现;
另一方面将 Component|Adapter 拆分为 View,Reducer,Effect 等相互独立的上下文无关函数。
所以它会非常干净,易维护,易协作。
Fish Redux 的灵感主要来自于 Redux, Elm, Dva 这样的优秀框架。而 Fish Redux 站在巨人的肩膀上,将集中,分治,复用,隔离做的更进一步。




2.7 Provide

和Scoped_model一样,Provide也是借助了InheritWidget,将共享状态放到顶层MaterialApp之上。底层部件通过Provier获取该状态,并通过混合ChangeNotifier通知依赖于该状态的组件刷新。
Provide还提供了Provide.stream,让我们能够以处理流的方式处理数据,不过目前还有一些问题,不推荐使用。




2.6 Provider 【推荐👍👍👍】

 2019 Google I/O 大会上重磅消息出了支持 flutter_web 之外,另一个便是弃用之前的状态管理 Provide,转而推荐相似的库 Provider;虽然只有一个字母之差使用方式差别却很大;小菜初步学习一下新的状态管理库 Provider;
 Flutter 针对不同类型对象提供了多种不同的 Provider;Provider 也是借助了 InheritWidget,将共享状态放到顶层 MaterialApp 之上;






3 实际开发中遇到的坑

3.1 ⚠️Provider中如何抉择 Consumer 还是 Selector

当我们需要更新页面的时候,我们会用notifyListeners();
但是如果我们用Consumer来包裹一个listview我们打印会发现已经build过的item都会重新的build一次。
其实这样有一点不合理,因为我们只需要更新的是其中一个item。
这时候如果我们用Selector来处理的话就会发现大有不同。它只会build我们需要更新的那个item这样的话就更加合理。
但是使用Selector的话,需要注意的是shouldRebuild用什么来决定是否重新build


3.2 ⚠️Provider使用Provider.of(context) 获取顶层数据的坑

如果我们APage使用Provider.of(context) 获取顶层数据,然后BPage对数据进行了更改,其实会影响到APage的然后会重新运行其 build。
其实我们command点击of进到底层代码发现,除了context以外还有一个参数listen。
如果我们在APage设置listen:false,这样就不会因为BPage的操作影响到APage了


3.3 ⚠️SingleTickerProviderStateMixin与TickerProviderStateMixin的坑

当使用vsync: this的时候,State对象必须with SingleTickerProviderStateMixin或TickerProviderStateMixin

首先我们要搞清楚SingleTickerProviderStateMixin于TickerProviderStateMixin的区别:
TickerProviderStateMixin适用于多AnimationController的情况
SingleTickerProviderStateMixin适用于单个AnimationController的情况

其实非必须用TickerProviderStateMixin,建议是用SingleTickerProviderStateMixin的。
1.因为如果 APage 使用 with TickerProviderStateMixin,当从APage 跳转到BPage的时候或从BPage返回到APage的时候你打印会发现都调用了build方法。
2.如果不使用 with TickerProviderStateMixin,当从APage 跳转到BPage的时候或从BPage返回到APage的时候都不会调用了build方法。











参考来源:

State management

Flutter移动应用:状态管理

Flutter | 状态管理指南篇——Provider

八种 Flutter 状态管理-深入评论

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