Flutter与MobX的那些事

2022-05-10 20:38:45 浏览数 (2)

开始

在以前的一篇文章中,半行代码 介绍到在 Flutter 里面使用 MobX, 今天我们就来聊聊 Flutter 和 MobX 的那些事。

这篇文章的大部分内容可以在 MobX.dart[1] 中看到更原汁原味的介绍。阅读本篇文章阅读约需 30 分钟。

我们先来看看 MobX 是什么,根据README的介绍

代码语言:javascript复制
使用透明的函数响应式编程增强 Dart 程序中的状态管理

是前端里大名鼎鼎的 MobX.js 的 Dart 版本。

概念

那么,MobX.Dart 有哪些概念,反应了自己函数响应式编程的特性呢?

这里关系到 MobX 的 3 个重要概念:

•Observables: Observables 表示响应式的状态。状态很容易理解,就是应用程序里面的状态,或者数据。响应式,就是可以感知到,可观察到数据的变化,也就是我们经常接触到的 观察者模式

•Actions: Actions 就是一系列可以引发状态发生变化的动作

•Reactions:上面提到状态是可观察的,那么这里的 Reactions 就是状态的观察者,状态发生改变的时候,他们可以收到数据变化的通知。

关于 Observables,还有一些更多的概念:

Computed Observables

顾名思义,被计算出来的 Observables。在一个应用中,我们实际上有 2 种状态,这里分别称为 core-statederived-statecore-state 指的就是一个对象原本就存在的状态,例如一个人有自己的firstname和lastname,姓和名是人固有的状态。那么 firstname lastname,就动态的出来了我们”姓名“的结果。这个就是我们的 derived-state。当然这里是一个很基础的例子,现实中我们的业务可能是几个固有属性,通过一个复杂的计算算出了一个最终的结果。这个最终结果实际上也是这个对象的一种状态,它也需要被感知到变化。

这里套用 MobX 的一张图来表示他的核心概念:

show code

我们来看看 Mobx 的具体用法,套用 Flutter 默认的 计数器点击 1 的例子。

一个简单的计数器可以表示成一个可观察的数字状态,计数器表示为 Counter 对象:

代码语言:javascript复制
part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value  ;
  }
}

这里,Mobx 需要借助 builder_runner 这个库生成对应的 _$Counter 类的代码。具体可以自行 Google。

那么示例中计数器的值怎么反应到 UI 呢?这里就要借助我们 Flutter-MobX 里的 Widget 啦

代码语言:javascript复制
final counter = Counter(); // Instantiate the store

Observer(
    builder: (_) => Text('${counter.value}',
    style: Theme.of(context).textTheme.display1,
    ),
),

这里我们使用了一个叫做 Observer 的 Widget, builder方法里面把 counter 的 observable 对象的值作为属性传给 Text。

这里在调用 @action 的函数之后,counter 里面的值变化后会自动体现在 UI 上。

这里我们可以看到,使用了 MobX,我们可以尽可能的使用 StatelessWidget,避免了 setState 的混乱。也提升了效率。

那么如何使用 Reaction 完成对他的监听呢,Reaction相关的函数有好几个,这里列举几个比较典型的:

ReactionDisposer autorun(Function(Reaction) fn)

立即执行 fn

代码语言:javascript复制
import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = autorun((_){
  print(greeting.value);
});

greeting.value = 'Hello MobX';

dispose();

这里会输出

代码语言:javascript复制
Hello World
Hello MobX

ReactionDisposer reaction<T>(T Function(Reaction) predicate, void Function(T) effect)

监听 predicate 里面监听 observable,当 predicate 返回了新的对象的时候,调用 effect 函数。

代码语言:javascript复制
import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = reaction((_) => greeting.value, (msg) => print(msg));

greeting.value = 'Hello MobX'; // Cause a change

dispose();

输出:

代码语言:javascript复制
Hello MobX

ReactionDisposer when(bool Function(Reaction) predicate, void Function() effect)

待条件的响应。在reaction的基础上加上 predicate 函数返回 true

最佳实践

使用了 MobX,那么我们的代码该如何组织呢?一般来说,我们的 Store 会按照职责,分到每个业务相关的 Store 去。

那么,一个业务模块,如何组织它和 UI、逻辑呢?官方给出了建议的方式。将 Widegt - Store - Service 结合在一起。

•Widget:UI,状态的可视化表示•Store:处理状态•Service:逻辑操作,包括复杂逻辑,网络请求,本地数据库存储等等

最佳的代码结构如下:

其中:

UI 层应该尽量使用 StatelessWidgetObserver 结合, 减少 Widget 的 rebuild 次数,提升性能。

Store里面放的 @observable 对象,因为 Dart 在 Flutter 是不能进行运行时反射的,所以复杂对象需要我们自己进行 observable 的声明。否则不会生效。当需要处理衍生状态的时候,可用 computed 替代。

到这里,其实我们在使用 MobX 的时候可以组织出职责分层很明确的函数响应式应用架构。但是不同的页面如何持有 Store 对象,也成了一个问题,当然这个问题在所有的分层架构里都存在。

最简单的是直接写单例的 store, 但是单例的弊端非常明显。我们需要的是在这几个页面这个对象是同一个,超出这个范围,对象可以销毁,或者使用的是另一个对象。很直接的我们就会需要一个对象管理框架,即 依赖注入

针对这点,官方也给出了自己的建议,可以使用 Provider 这个框架达到依赖注入的目的。在他的官方文档介绍里,也说自己是一个介于 DI 和 状态管理之间的框架。在这篇文章就不赘述 Provider的使用,感兴趣的朋友可以查看:provider的文档[2]

小结

使用 MobX,我们可以快速的上手,用一种很简便,容易组织的方式进行 Flutter 的状态管理和代码架构的统一。而且 Store 天然的分开可以写一堆。不会存在 Redux 顶级状态管理难以分而治之的问题。对于异步场景的处理也比 Redux 简单。比较推荐给大家。

References

[1] MobX.dart: https://mobx.pub/getting-started [2] provider的文档: https://pub.dartlang.org/packages/provider

0 人点赞