⚠️Flutter的 状态管理⚠️

2,169 阅读8分钟

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

1 对于状态管理的理解

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



1.0 简单理解

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

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

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

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

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是一种单向数据流架构,可以轻松开发,维护和测试应用程序。

我们在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方式构建应用的方法,这是一个由流构成的完全异步的世界。

* 用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 状态管理-深入评论