Understanding of state management in Flutter

### 1. State management cognition

1. Core decoupling

The core idea of ​​state management I understand is decoupling. Like other frameworks, Flutterthe state management in the framework is mainly to decouple logic and data to adapt to the development and maintenance of complex businesses.

2. Compared with other frameworks

The most classic decoupling is MVC, which is divided into three modules:

  • Model layer: data layer
  • View layer: page UI
  • Controller: Many logic processing is now using this classic mode. MVCThe mode can solve most of the coupling problems, but it is also Controllertoo bloated, so later developed the MVVM, MVPetc. mode, which is used to update in different scenarios. Nice decoupling.

3. Flutter state management

FlutterThe state management in is mainly to divide 逻辑层, 状态层, 页面层, 行为层etc.:

  • view: interface layer, mainly UI
  • Logic: logic layer, mainly deals with business logic
  • State: The state layer, which mainly deals with the data state required by the page
  • Action: Behavior layer, which mainly handles interaction events
  • Reducer: This level is dedicated to handling data changes

Different decoupling layers can be combined into different modes, MVCsuch as logic layer, data layer, and page layer, which can be divided into minimalist mode (page layer + logic layer) and standard mode (page layer + logic layer) in Flutter + state layer), strict mode (page layer + logic layer + state layer + behavior layer), obsessive-compulsive disorder mode (page layer + logic layer + state layer + behavior layer + Reducer layer), several modes have their own advantages and disadvantages, as follows I will 状态管理框架talk about these modes in detail in combination with specific details .

###2. State management mode

2.1. Minimalist mode

状态管理-极简模式.png

从上图可以看出极简模式由逻辑层+页面层组成,各自的职责如图所示,非常的简洁,对于不是太复杂的业务使用与用该模式开发,如果一些简单的页面也用复杂的模式感觉有点生搬硬套了,有的分层完全是不需要的,也造成了内存的浪费。 现在较为流行的状态管理框架也是按照极简模式划分的,如providergetx,两者的区别不大,不过在选择使用时选provider可能需要能hold的住InheritedWidget,选getx可能需要能hold的住依赖注入,回收GetXController

2.2、标准模式

状态管理-标准模式.png

从上图可以看出标准模式由 逻辑层+页面层+状态层组成,各自的职责如图所示,相比极简模式,标准格式多了状态层,状态层主要是存储所需的状态变量。标准模式类似于经典的MVC的分层,用的十分普遍,能够很好的解耦。 常见的框架有cubitprovidergetx

2.3、严格模式

状态管理-严格模式.png

从上图可以看出标准模式由 逻辑层+页面层+状态层+行为层组成,各自的职责如图所示,相比标准模式,严格格式多了一个行为层,行为层从图中可以清楚的看出主要是为了处理交互事件,那为什么要多出这一层呢?其实在Flutter中使用标准模式时会存在一个问题,很多的交互事件都是深埋在各个widget中,查找起来非常不变,事件少了还好,如果后续越来越复杂事件越来越多那就成了一团麻了。有了行为性这一分层,可以很好的处理交互事件,页面有什么交互事件,交互事件如何处理只需到Action文件中查找即可,对于后期的维护来说十分方便。 常见的状态管理框架:BlocReduxfish_redux

2.4、强迫症模式

状态管理-强迫症模式.png

从上图可以看出标准模式由 逻辑层+页面层+状态层+行为层+Reducer层组成,各自的职责如图所示,相比严格模式,强迫症模式多了一个Reducer层Reducer层主要是对数据进行处理并更新后刷新页面。从上图来看这个结构已经有点复杂了,为了解耦数据刷新这一层次,付出了巨大的成本 常见的状态管理框架:Reduxfish_redux

###三、状态管理框架对比

3.1、Bloc

BLoC是谷歌提出的一种设计模式,利用stream流的方式实现界面的异步渲染和重绘,我们可以非常顺利的通过BLoC实现业务与界面的分离。在使用BLoC前需要了解三个重要的概念,分别是 CubitBlocObserverBLoC

3.1.1、Cubit

Cubit是管理状态数据的 BlocBase 子类,它可以管理任意类型的数据,包括基本类型到复杂对象。在Cubit调用 emit 构建新的状态数据前需要给状态数据一个初始值。当状态数据发生改变的时候,会触发 Cubit 的 onChange 回调,如果出现错误则会触发 onError 回调。

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  void onChange(Change<int> change) {
    super.onChange(change);
    print(change);
  }

  @override
  void onError(Object error, StackTrace stackTrace) {
    print('$error, $stackTrace');
    super.onError(error, stackTrace);
  }
}
复制代码
3.1.2、BlocObserver

BlocObserver 可以监听所有的Cubit的变化,从而使得我们可以同时监听多个Cubit

3.1.3、Bloc

Bloc也是继承 BlocBase 的类,但相比 Cubit 来说更为高级一些。它使用的是 events 而不是暴露的函数来更新状态。在 Bloc 内部,有一个onEvent 方法,在这个方法里,通过 EventTransformerevent转换为更新状态的方法来刷新状态数据。每个event都可以有对应的 EventHandler来处理该 event,完成后再通过 emit 触发通知状态更新。当状态转变前会调用 onTransition,在这里会有当前的状态,触发更新的 event 和下一个状态。

Bloc 的设计来看,使用了函数Cubit形式和事件Bloc形式的方式来驱动状态数据更新,然后再通过emit通知状态更新,通过这种方式解耦 UI 界面和业务逻辑。

优缺点分析:
优点:
  • BLoC的目录结构清晰,完全符合mvvm的习惯。对于工程化项目来说会比较受欢迎,团队协作起来会减少出错的概率,大家都跟着一个模式去做,维护性也提高了;
缺点:
  • BLoC使用起来相对复杂,需要创建多个文件。虽然官方引入了cubit,把event组合到bloc文件中,但强烈的结构化依然让不少初学者难以入门;
  • 颗粒度的把控相对困难。通过BlocBuilder构建的视图,在state变更时,视图都会rebuild,想要控制颗粒度只能把bloc再拆细,这会极大的增加代码复杂度和工作量;不过这个问题可通过引入freezed生成代码,然后通过buildWhen等方式减少视图刷新的频次。

3.2、Provider

Provider是Flutter官方开发维护的,也是近些年官方最为推荐的状态管理库。它对InheritedWidget进行了上层封装,致力解决原生setState方案的props臃肿、展示与逻辑耦合问题。使用时需在项目中引入provider这个库。

Provider将页面分为业务和视图两层,并定义NotifierConsumer两个核心概念:Notifier负责实现业务逻辑,且在数据更新时发出通知。Consumer负责实现界面逻辑,并在数据更新时更新自身,以及用户交互时调用Notifier方法。

优缺点分析:
优点:
  • 基于官方InheritedWidget的封装,不存在任何风险,很稳定且不会给性能方面加负担;
  • 方案涉及概念少,上手成本低;
缺点:
  • 数据流分为业务、视图两层。项目规模变大时,业务层复杂度容易指数级增长;
  • context强关联,有Flutter开发经验的都知道,context大多时候基本都是在widget中才能获取到,在其他地方想随时获取 BuildContext 是不切实际的,也就意味着大多时候只能在view层去获取到Provider提供的信息。

3.3、GetX

GetX 是 Flutter 上的一个轻量且强大的解决方案,也是我现在项目中正在使用的框架,在Flutter状态管理中绝对算是异军突起,一经发布就因其简单且全面的优势,引得一大批簇拥者。GetX可以称之为全家桶式的框架,具有以下多种功能:

  • 主题切换:比如深色模式切换;
  • 多语言:可以通过配置 Map 搞定多语言;
  • 弹窗提醒:包括了 SnackBar 和对话框;
  • 路由:无需 context 的路由跳转;
  • 离线存储:不依赖原生的key-value 存储组件的离线存储 GetStorage
  • 状态管理:快速接入的响应式状态管理;
  • 工具类:例如表单验证工具,获取系统参数(平台类型,屏幕尺寸等);
  • 依赖注入容器:使用简单的 putfind 方法完成容器对象的注册和获取;
  • 网络请求:可以使用 GetConnect 完成网络请求。
3.3.1、路由管理

GetX内部实现了路由管理,这个是非常重要的,这样我们就不需要使用其他第三插件,而且GetX的路由管理非常简单,代码也简洁。

/// 跳转新页面
/// 第一种方式 进入新页面 直接页面
Get.to(TestPage());
/// 第二种方式 进入新页面 配置路由名称  建议这种统一配置
Get.toNamed(Routes.TestPage);
/// 弹出当前页,并将一个新的[page]推入堆栈,就是删除就页面,使用新页面
Get.off(TestPage());
/// Push a [page]和弹出几个页面在堆栈中,就是进入新页面,删除之前进栈的页面。比如场景(注册-手机号-其他注册信息-注册完了直接到主页,之前页面全部删掉。)
Get.offAll(TestPage());
/// 同上,就是传路由名称
Get.offAllNamed(FXRoutes.TestPage);
/// 返回上一面 就一句
Get.back()
复制代码
3.3.2、状态管理

使用getx的状态管理,我一般一个page对应一个logic, logic需要继承 GetxController,该logic来处理逻辑并控制page,目录如下: GetX状态管理.png

将页面和logic管理起来GetX使用的是依赖注入,有两种方式可以实现: 1、在view中使用依赖注入和logic关联,然后在获取到logic中的state,从而view中的交互事件直接调取logic中的方法进行处理,state用于刷新页面UI。

final logic = Get.put(TestPageLogic());
final state = Get.find<TestPage>().state;
复制代码

2、在路由中绑定

GetPage(
        name: FXRoutes.TestPage,
        page: () => TestPage(),
        /// 主要代码是这个 绑定
        binding: BindingsBuilder(() => {
              Get.lazyPut<TestPageLogic>(
                  () => TestPageLogic())
            })),
复制代码

使用GetView可以直接使用logic

/// 页面继承GetView<> 传依赖注入的控制器 这样就可以直接使用了,会发现这边没有 Get.put,或者Git.find, 使用的时候直接logic。 看源码可以知道GetView内部已经帮我们实现了。
class TestPage extends GetView<TestPageLogic> {
 @override
  Widget build(BuildContext context) {

  }
}
复制代码
优缺点分析:
优点:
  • 使用简单,用起来确实很简单,极易上手;脱离context,随时随地想用就用,解决了BLoC和Provider的痛点;
  • 全家桶式功能,使用GetX后,我们无需再单独去做路由管理、国际化、主题、全局context等;
缺点:
  • 使用 GetX 的导航需要使用自定义的 MaterialApp 或 CupertinoApp,也就是我们需要使用 GetMeterialApp 或 GetCupertinoApp 包裹应用才能够在页面跳转时无需使用 BuildContext。对应用的侵入性比较强;
  • 使用GetX的话需要能hold住依赖注入。

3.4、fish_redux

fish_redux是阿里咸鱼团队开发的一个状态管理框架,是基于 Redux 数据管理的组装式 flutter 应用框架, 它特别适用于构建中大型的复杂应用。

它的特点是配置式组装。 一方面我们将一个大的页面,对视图和数据层层拆解为互相独立的 Component|Adapter,上层负责组装,下层负责实现; 另一方面将 Component|Adapter 拆分为 View,Reducer,Effect 等相互独立的上下文无关函数,结构非常清晰,适合团队开发,易与后期的维护。

使用fish_redux进行开发,拆分的文件目录为:

  • action.dart 事件转发动作类;
  • effect.dart 网络请求、逻辑处理;
  • page.dart 做一些配置工作;
  • reducer.dart 简单的对数据的操作及更新;
  • state.dart 数据管理类;
  • view.dart 视图类。

fish_redux.png

在开发中可下载 FishReduxTemplate插件,可快速生成对应的文件: FishReduxTemplateu.png

fish_redux相比其它框架多了actionreducer层,其中action层主要是行为层,统一转发处理交互事件,我认为这一层还是很有必要的,多了一层从框架端来说复杂度是提高了,但是对于使用者来说却是结构清晰了,如果没有行为层,交互事件大多都埋在各种widget中,这样找起来非常不方便,而且随着后期的迭代也会变得越来越难维护,有了行为层就清晰多了。对于reducer层我保留意见,可能是水平达不到,我没有感受到这层妙处在哪。

优缺点分析:
优点:
  • 结构清晰,适合团队开发,也利于后期维护;
缺点:
  • 更新速度较差,我们项目已放弃fish_redux,因为3.0版本还没有适配空安全;
  • 结构会较为复杂,上手难度大,学习成本较高。

[注]:选择哪种状态管理框架没有准确的答案,其受难易程度、可维护性、开发成本、性能、应用场景、团队技术栈等因素的影响,所以适合自己的才是最好的。

Guess you like

Origin juejin.im/post/7225915335785627708