Flutter 状态管理框架对比

Flutter 本身就有非常强大的状态管理方式 setState,但是如果用于多组件的开发,通过 setState 实现子组件通信是非常麻烦的。

框架信息

状态管理框架 Pub like GitHub star 状态更新 共享状态 颗粒度 说明
setState - - Yes No - 适用于较小规模 widget 的暂时性状态的基础管理方法
StreamController - - Yes No 变量 基于流/观察者模式的基础管理方法
RxDart 712 2600 Yes No 变量 基于流/观察者模式的框架
MobX 224 1700 Yes No 变量 基于观察及响应的状态管理常用库
GetX 1723 1300 Yes Yes 变量 轻量级响应式状态管理解决方案
Provider 2488 3000 Yes? Yes 官方推荐的状态共享框架
flutter_bloc 1236 5700 Yes Yes 基于流/观察者模式的框架
flutter_redux 164 1300 Yes Yes 前端开发者较为熟悉的状态容器实现。
Fish-Redux 34 6800 Yes Yes 基于 Redux 状态管理的组合式 Flutter 应用框架

计数器示例

通过简单的计数器示例对比几个框架的实现,部分框架是不带有状态共享的,所以会配合Provider一起实现。计数器本身是一个非常简单的示例,为了凸显状态管理的意义,我会把“计数器值视图”和“点击按钮”拆分为2个组件,让结构变得复杂一些,就是一个页面加两个组件的方式。

setState

SDK中最简单的更新方式,不同组件之间的通信只能通过构造函数和回调实现。

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

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

class _CounterPageState extends State {
  var counter = 0;

  @override
  Widget build(BuildContext ct) {
    return Scaffold(
      body: Center(
        child: CounterText(counter),
      ),
      floatingActionButton: Button(
        onPressed: () {
          setState(() {
            counter++;
          });
        },
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  final int count;

  CounterText(this.count);

  @override
  Widget build(BuildContext context) {
    return Text('$count');
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  final VoidCallback onPressed;

  Button({@required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: onPressed,
      child: new Icon(Icons.add),
    );
  }
}

StreamController+InheritedWidget

这个示例是通过StreamController和InheritedWidget结合从而实现状态更新和状态共享。

/// 用于实现Widget树共享状态状态
class MyProvider extends InheritedWidget {
  final dynamic value;

  MyProvider({this.value, Widget child}) : super(child: child);

  @override
  bool updateShouldNotify(covariant MyProvider oldWidget) {
    return oldWidget.value != this.value;
  }

  static T of(BuildContext context) {
    MyProvider provider =
        context.dependOnInheritedWidgetOfExactType(aspect: MyProvider);
    return provider.value;
  }
}

class CounterBloc {
  StreamController _counterController = StreamController();
  int counter = 0;

  Stream get counterStream => _counterController.stream;

  increment() {
    _counterController.add(++counter);
  }

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


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

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

class _CounterPageState extends State {
  var _counterBloc = CounterBloc();

  @override
  void dispose() {
    _counterBloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext ct) {
    return MyProvider(
      value: _counterBloc,
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var bloc = MyProvider.of(context);
    return StreamBuilder(
      stream: bloc.counterStream,
      initialData: bloc.counter,
      builder: (context, snapshot) {
        return Text('${snapshot.data}');
      },
    );
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var bloc = MyProvider.of(context);
    return FloatingActionButton(
      onPressed: bloc.increment,
      child: new Icon(Icons.add),
    );
  }
}

RxDart+Provider

由 ReactiveX 团队开发的,是一个 dart 语言的 Rx 框架,使用极其轻量级,如果需要共享状态能力建议配合 Provider 一起使用。从下面的示例可以看到,RxDart的用法是接近于 StreamController,其实 BehaviorSubject 就是实现 StreamController,进行了高级的封装,加入了很多简便的用法。由于RxDart不具备状态共享能力,所以结合了Provider实现这个示例。

class CounterBloc {
  var counterSubject = BehaviorSubject.seeded(0);

  increment() {
    counterSubject.value++;
  }

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

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext ct) {
    return Provider(
      create: (context) => CounterBloc(),
      
      /// 需要手动调动dispose
      dispose: (context, bloc) => bloc.dispose(),
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var bloc = Provider.of(context);
    return StreamBuilder(
      stream: bloc.counterSubject,
      initialData: bloc.counterSubject.value,
      builder: (context, snapshot) {
        return Text('${snapshot.data}');
      },
    );
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: (){
        Provider.of(context, listen: false).increment();
      },
      child: new Icon(Icons.add),
    );
  }
}

dependencies:

rxdart: ^0.24.1

MobX+Provider

flutter_mobx 和其它框架的实现都不太相同,它是基于注解实现,需要配合 mobx_codegen 使用的。需要添加 flutter_mobx、build_runner 和 mobx_codegen 依赖。

dependencies:
  flutter_mobx: ^1.1.0+2

dev_dependencies:
  build_runner:
  mobx_codegen: ^1.1.1+1

创建 counter.dart 文件。

/// counter.dart

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {
  @observable
  int counter = 0;

  @action
  void increment() {
    counter++;
  }
}

执行命令,就会生成一个 counter.g.dart 文件

flutter packages pub run build_runner build

MobX 的用法很简洁,但是每次修改文件都需要执行 build_runner 命令,如果项目很大,执行时间很长,这个也是一个问题。由于MobX不具备状态共享能力,所以结合了Provider实现这个示例。

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => CounterStore(),
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var store = Provider.of(context);
    return Observer(
      builder: (context) {
        return Text('${store.counter}');
      },
    );
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: (){
        Provider.of(context, listen: false).increment();
      },
      child: new Icon(Icons.add),
    );
  }
}

#### GetX+Provider
这个框架非常简洁,而且设计的思路和MobX很相似,不同的是,GetX不需要使用 build_runner 技术即可实现相同的效果。不过GetX的功能远不止如此,它提供了入侵性很强的用法,有国际化、主题、路由等功能,功能涉及非常广泛。由于GetX不具备基于Widget树的状态共享能力,所以结合了Provider实现这个示例。
```dart
class CounterController extends GetxController {
  var counter = 0.obs;

  increment() => counter++;
}

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => CounterController(),
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var controller = Provider.of(context);
    return Obx(() => Text('${controller.counter}'));
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: (){
        Provider.of(context, listen: false).increment();
      },
      child: new Icon(Icons.add),
    );
  }
}
```
> dependencies:
>   get:

#### Provider
这个框架是 [Google I/O 2019](https://www.youtube.com/watch?v=d_m5csmrf7I) 上推荐的这个框架,并写入了 [Flutter 的官方开发文档](https://flutter.cn/docs/development/data-and-backend/state-mgmt/simple)。这个框架主要是用于不同组件之间实现状态共享,不过它也提供了一个ChangeNotifier 轻量级类,可以用于实现状态更新。
```dart
class CounterModel extends ChangeNotifier {
  int counter = 0;

  increment() {
    counter++;
    // 通知视图刷新
    notifyListeners();
  }
}

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer(builder: (context, counterModel, child) {
      return Text('${counterModel.counter}');
    });
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        Provider.of(context, listen: false).increment();
      },
      child: new Icon(Icons.add),
    );
  }
}
```


> dependencies:
>   provider: ^4.3.2+2

bloc

bloc 的用法和 Provider 很相似,也提供不同组件之间实现状态共享,也提供了状态更新功能。

class CounterCubit extends Cubit {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
}

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder(builder: (context, count) {
      return Text('$count');
    });
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        context.bloc().increment();
      },
      child: new Icon(Icons.add),
    );
  }
}

Redux

这个相比以上的框架,是复杂一些的,其思想源于前端开发。

enum Actions { Increment }

int counterReducer(int state, dynamic action) {
  if (action == Actions.Increment) {
    return state + 1;
  }

  return state;
}

class CounterPage extends StatelessWidget {
  CounterPage({Key key}) : super(key: key);

  final store = new Store(counterReducer, initialState: 0);

  @override
  Widget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: Scaffold(
        body: Center(
          child: CounterText(),
        ),
        floatingActionButton: Button(),
      ),
    );
  }
}

/// 计数器值视图
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreConnector(
      converter: (store) => store.state.toString(),
      builder: (context, count) {
        return Text(count);
      },
    );
  }
}

/// 点击按钮
class Button extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new StoreConnector(
      converter: (store) {
        return () => store.dispatch(Actions.Increment);
      },
      builder: (context, callback) {
        return new FloatingActionButton(
          onPressed: callback,
          child: new Icon(Icons.add),
        );
      },
    );
  }
}

fish_redux

闲鱼开源基于Redux思想的框架,但是比官方Redux要复杂很多。这个项目虽然 star 很多,但是半年没有更新了,还处于 0.3.4 版本,被提了130多个 Issue,也没有人去处理,估计闲鱼团队自己都已经放弃这个框架了吧,建议不要使用这个框架。

总结

状态管理的框架很多,Redux的思想和其它的都不太一样,由于我对Redux的思想没有足够深入的了解,这里就不做更多的评价。Flutter还是推荐BLoC思想的,基于颗粒度主要是分为2类,“变量”和“类”。如果需要涉及很多状态的改变,如果颗粒度为“变量”,每一个状态只需要创建一个变量即可,但是也有一个缺点,需要搭配Provider一起使用,自动管理能力较差。对于颗粒度为“类”的框架,要么就是牺牲更新的颗粒度,定义一个包含很多状态的State,缺点就是无法做到小颗粒更新,一个小小的改变,所有依赖这个State的地方都需要刷新。要么就只能为每一个状态创建一个class,有多少状态就有多少个class,缺点就是整体代码量大大增加。我个人的偏向是使用颗粒度为变量的RxDart框架,搭配Provider,使用更加灵活。 虽然 MobX 更加优雅,但是 MobX 是基于注解,业务层本身就经常需要修改,用起来成本较高。GetX 确实是最优雅的,值得去研究。

你可能感兴趣的:(Flutter 状态管理框架对比)