Mobx是什么
mobx的作用可以使用一句话来概括:使用透明的函数响应式编程增强Dart程序中的状态管理
,是前端中大名鼎鼎的Mobx.js的Dart版本。
Mobx的三个重要概念
- Observables: 表示响应式的状态,也可理解为可观察对象。状态指的是应用程序里面的状态或者数据。响应式就是可以感知到、可观察到数据的变化,也就是我们经常接触到的观察者模式。
- Actions: Actions就是一系列可以引发状态发生变化的动作。
- Reactions: 状态的观察者,状态发生变化的时候,他们可以收到数据变化的通知。
具体用法
以计数器为例,一个简单的计数器可以表示成一个可观察的数字状态,计数器表示为Counter对象。
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类的代码。
计数器中的值需要通过Flutter_Mobx里面的Widget来反应到UI,使用Observer来包裹需要观察的组件:
final counter = Counter(); // Instantiate the store
Observer(
builder: (_) => Text('${counter.value}',
style: Theme.of(context).textTheme.display1,
),
),
这里使用了一个叫做Observer的Widget,builder方法中把counter的observable对象的值作为属性传给Text。
官方建议
官方建议,将widget-store-service结合在一起。
Widget:UI,状态的可视化表示
Store: 处理状态
Service: 逻辑操作,包括复杂逻辑,网络请求,本地数据库存储等等。
- UI层应该尽量使用StatelessWidget和Observer结合,减少Widget的rebuild次数,提升性能。
- Store里面放的 @observable 对象,因为 Dart 在 Flutter 是不能进行运行时反射的,所以复杂对象需要我们自己进行 observable 的声明。否则不会生效。当需要处理衍生状态的时候,可用 computed 替代。
关于不同的页面如何持有Store对象的问题
最简单的是直接写单例的store,但是单例的弊端非常明显,我们需要的是在这几个页面中此对象是同一个,超出这个范围,对象可以销毁,或者使用的是另一个对象,很直接的我们就会需要一个对象管理框架,即依赖注入。
针对这点,官方给出的建议是,可以使用Provider这个框架达到依赖注入的目的,可以查看https://pub.dev/packages/provider 。
原理
首先,在上面的计数器例子中,看到新生成的类_$Counter,其中部分代码如下:
@override
int get value {
_$valueAtom.reportRead();
return super.value;
}
@override
set value(int value) {
_$valueAtom.reportWrite(value, super.value, () {
super.value = value;
});
}
可以看到在获取变量时,会调用reportRead()
, 设置变量会调用reportWrite
。
我们先看看reportRead()做了什么,
void reportRead() {
context.enforceReadPolicy(this);//1
reportObserved();//2
}
第一行代码是为了确保观察值在Actions和Reactions内部读取。重点在于第二行调用的reportObserved
方法,其代码如下:
//atom可以理解为对应的被观察对象的封装
void _reportObserved(Atom atom) {
final derivation = _state.trackingDerivation;
if (derivation != null) {
derivation._newObservables.add(atom);
if (!atom._isBeingObserved) {
atom
.._isBeingObserved = true
.._notifyOnBecomeObserved();
}
}
}
可以看出来这段代码做的事情就是:把当前的变量加入到被观察的队列中去,如果变量未被观察,则把此变量设置为观察状态。
再来看看reportWrite做了什么:
void reportWrite(T newValue, T oldValue, void Function() setNewValue) {
context.spyReport(ObservableValueSpyEvent(this,
newValue: newValue, oldValue: oldValue, name: name));
// ignore: cascade_invocations
context.conditionallyRunInAction(() {
setNewValue();
reportChanged();
}, this, name: '${name}_set');
// ignore: cascade_invocations
context.spyReport(EndedSpyEvent(type: 'observable', name: name));
}
其核心是执行了reportChanged方法,
void reportChanged() {
_context
..startBatch()
..propagateChanged(this)
..endBatch();
}
来看看propagateChanged()
中都做了些什么:
void propagateChanged(Atom atom) {
if (atom._lowestObserverState == DerivationState.stale) {
return;
}
atom._lowestObserverState = DerivationState.stale;
for (final observer in atom._observers) {
if (observer._dependenciesState == DerivationState.upToDate) {
observer._onBecomeStale();//核心
}
observer._dependenciesState = DerivationState.stale;
}
}
当数据需要更新的时候,调用观察者的_onBecomeState方法。查看observable.dart并追踪下去(过程过于复杂,这里不多赘述),可以发现最终就是调用了reaction的run方法,
for (final reaction in remainingReactions) {
reaction._run();
}
如下:
@override
void _run() {
if (_isDisposed) {
return;
}
_context.startBatch();
_isScheduled = false;
if (_context._shouldCompute(this)) {
try {
_onInvalidate();
} on Object catch (e) {
// Note: "on Object" accounts for both Error and Exception
_errorValue = MobXCaughtException(e);
_reportException(e);
}
}
_context.endBatch();
}
其中的_onInvalidate()就是在observer构成的时候传入的方法:
void _invalidate() => setState(noOp);
static void noOp() {}
看到这里,我们发现就是通过调用setState从而刷新了widget。
参考资料
- https://juejin.cn/post/6844903860184563720
- https://takeroro.github.io/2020/06/30/mobX%20flutter%20%E6%95%B0%E6%8D%AE%E6%B5%81%E5%8A%A8/
- https://cn.mobx.js.org/