Flutter 的状态管理框架很多,笔者个人使用起来比较舒适的是 flutter_mobx,使用了不短的时间,最近抽时间了解了一下 flutter_mobx 的实现。今天在这里分享一下。如果你还不熟悉 flutter_mobx 的使用,可以参考它的文档:https://pub.dev/packages/flutter_mobx,也可以参考我之前写的一篇文章: Flutter与Mobx那些事
如果你也使用过,那么可以继续往下看。我们看最经常使用的 counter 计数 demo 的 mobx 实现,我们的代码里会有一个 counter
变量表示计数:
数据写入和响应
代码语言:javascript复制@observable
int counter;
@observable 最后在生成的 mobx 代码里,对应的是 Atom
对象:
final _$countAtom = Atom(name: '_Counter.count');
@override
set count(int value) {
_$countAtom.reportWrite(value, super.count, () {
super.count = value;
});
}
这里的 Atom
写入值的流程比较复杂,可以概括为图中的结构:
image.png
Atom
内部结构主要包括
- name debug用到的名称
- context 上下文
- observers 观察者
赋值操作最终会在一个 ActionController 里面完成:
代码语言:javascript复制void conditionallyRunInAction(void Function() fn, Atom atom,
{String? name, ActionController? actionController}) {
if (isWithinBatch) {
enforceWritePolicy(atom);
fn();
} else {
final controller = actionController ??
ActionController(
context: this, name: name ?? nameFor('conditionallyRunInAction'));
final runInfo = controller.startAction();
try {
enforceWritePolicy(atom);
fn();
} finally {
controller.endAction(runInfo);
}
}
}
这么能看到这么几点:
- 如果存在
ActionController
了,说明这个赋值逻辑是发生在我们的 @action 方法里的,直接在这里执行。 - 如果对
Atom
的写值不在 action 内,需要创建一个ActionController
, 通过startAction
开始这个操作 - 执行检查逻辑,没问题的话执行 fn 回调。在
enforceWitePolicy
中,会检查是否有在计算中去观察变量的行为,有的话会报错。
startAction
和 endAction
则是一对操作:
// ActionController
ActionRunInfo startAction({String? name}) {
final reportingName = name ?? this.name;
_context.spyReport(ActionSpyEvent(name: reportingName));
final startTime = DateTime.now();
final prevDerivation = _context.startUntracked();
_context.startBatch();
final prevAllowStateChanges = _context.startAllowStateChanges(allow: true);
return ActionRunInfo(
prevDerivation: prevDerivation,
prevAllowStateChanges: prevAllowStateChanges,
name: reportingName,
startTime: startTime);
}
void endAction(ActionRunInfo info) {
_context.spyReport(EndedSpyEvent(
type: 'action',
name: info.name,
duration: DateTime.now().difference(info.startTime)));
// ignore: cascade_invocations
_context
..endAllowStateChanges(allow: info.prevAllowStateChanges)
..endBatch()
..endUntracked(info.prevDerivation);
}
在设置值的地方会调用 reportChanged
:
void reportChanged() {
_context
..startBatch()
..propagateChanged(this)
..endBatch();
}
这里会执行 endBatch
:
void endBatch() {
if (--_state.batch == 0) {
runReactions();
for (var i = 0; i < _state.pendingUnobservations.length; i ) {
final ob = _state.pendingUnobservations[i]
.._isPendingUnobservation = false;
if (ob._observers.isEmpty) {
if (ob._isBeingObserved) {
// if this observable had reactive observers, trigger the hooks
ob
.._isBeingObserved = false
.._notifyOnBecomeUnobserved();
}
if (ob is Computed) {
ob._suspend();
}
}
}
_state.pendingUnobservations = [];
}
}
当操作完成的时候,会执行 reactions, flutter_mobx 也是通过 runReactions
去响应数据的变化的。
runReactions
最后会执行 _runReactionsInternal:
void _runReactionsInternal() {
_state.isRunningReactions = true;
var iterations = 0;
final allReactions = _state.pendingReactions;
while (allReactions.isNotEmpty) {
if ( iterations == config.maxIterations) {
final failingReaction = allReactions[0];
_resetState();
throw MobXCyclicReactionException(
"Reaction doesn't converge to a stable state after ${config.maxIterations} iterations. Probably there is a cycle in the reactive function: $failingReaction");
}
final remainingReactions = allReactions.toList(growable: false);
allReactions.clear();
for (final reaction in remainingReactions) {
reaction._run();
}
}
_state
..pendingReactions = []
..isRunningReactions = false;
}
最后执行 Reaction
的 _run
:
@override
void _run() {
if (_isDisposed) {
return;
}
_context.startBatch();
_isScheduled = false;
if (_context._shouldCompute(this)) {
try {
_onInvalidate();
} on Object catch (e, s) {
// Note: "on Object" accounts for both Error and Exception
_errorValue = MobXCaughtException(e, stackTrace: s);
_reportException(_errorValue!);
}
}
_context.endBatch();
}
flutter_mobx 上下文
这里会执行 _onInvalidate 方法,负责监听的地方可以通过这个获取数据变化的回调。这里可以理解成 Atom
里面数据发生变化,是被观察的对象。Reaction
负责回调数据变化,可以理解成观察者。那么是谁把观察者和被观察者关联起来的呢?那么就是 Context
了,那么这个 Context
又是做什么的呢?Context
是在顶层函数中创建的,本质上是一个单例,是一个 ReactiveContext
对象的实例。ReactiveContext
对数据的追踪从 track
方法的调用开始:
void track(void Function() fn) {
_context.startBatch();
_isRunning = true;
_context.trackDerivation(this, fn);
_isRunning = false;
if (_isDisposed) {
_context._clearObservables(this);
}
_context.endBatch();
}
关键逻辑如下:
代码语言:javascript复制 // RectiveContext
T? trackDerivation<T>(Derivation d, T Function() fn) {
final prevDerivation = _startTracking(d);
T? result;
if (config.disableErrorBoundaries == true) {
result = fn();
} else {
result = fn();
}
_endTracking(d, prevDerivation);
return result;
}
// 这里先进入 _startTracking, 执行fn后就进入 _endTracking
Derivation? _startTracking(Derivation derivation) {
final prevDerivation = _state.trackingDerivation;
_state.trackingDerivation = derivation;
_resetDerivationState(derivation);
derivation._newObservables = {};
return prevDerivation;
}
void _endTracking(Derivation currentDerivation, Derivation? prevDerivation) {
_state.trackingDerivation = prevDerivation;
_bindDependencies(currentDerivation);
}
_startTracking里,上下文指定当前的Derivation _endTracking里面,对当前derivation绑定依赖。
代码语言:javascript复制 void _bindDependencies(Derivation derivation) {
final staleObservables =
derivation._observables.difference(derivation._newObservables!);
final newObservables =
derivation._newObservables!.difference(derivation._observables);
var lowestNewDerivationState = DerivationState.upToDate;
// Add newly found observables
for (final observable in newObservables) {
observable._addObserver(derivation);
// Computed = Observable Derivation
if (observable is Computed) {
if (observable._dependenciesState.index >
lowestNewDerivationState.index) {
lowestNewDerivationState = observable._dependenciesState;
}
}
}
for (final ob in staleObservables) {
ob._removeObserver(derivation);
}
if (lowestNewDerivationState != DerivationState.upToDate) {
derivation
.._dependenciesState = lowestNewDerivationState
.._onBecomeStale();
}
derivation
.._observables = derivation._newObservables!
.._newObservables = {};
}
这里做了2次diff,把新旧的observsable分开。旧的删除掉。新的添加 Derivation 去观察。那新的 newObservables
是怎么来的呢?是_reportObserved
:
void _reportObserved(Atom atom) {
final derivation = _state.trackingDerivation;
if (derivation != null) {
derivation._newObservables!.add(atom);
if (!atom._isBeingObserved) {
atom
.._isBeingObserved = true
.._notifyOnBecomeObserved();
}
}
}
这个逻辑会发生在atom的每一次 get 的时候。也就是get的时候就会把atom加到当前 derivation的观察队列里去。写数据的时候就按照开头的流程走去触发观察者回调。
流程清晰后职责也清楚了:
ReactiveContext
上下文,负责数据的绑定,分发,清理工作。把观察者和被观察者串起来的对象Reaction
反应对象,回调给观察者,持有 atom对象。可以理解成是观察者Atom
具体被观察的变量
image.png
flutter_mobx
flutter_mobx 提供了一个 Observer 组件,在这个组件里面使用 Store 里面的被观察对象,会自动去更新组件ui。
代码语言:javascript复制 Observer(
builder: (ctx) => Container(
child: Text(counter.count.toString()),
),
),
Obsever继承StatelessObserverWidget
,它的build方法返回我们传入的builder的结果。
StatelessObserverWidget 继承自 StatelessWidget,它的element是 StatelessObserverElement
本身逻辑则由混入的mix负责:ObserverWidgetMixin
ObserverWidgetMixin
内通过标记重建来刷新 Element:
void invalidate() => markNeedsBuild();
这时候最外面的 Observer
就会重新build。
在 Element 绑定的时候,会创建一个观察者:
代码语言:javascript复制 @override
void mount(Element? parent, dynamic newSlot) {
_reaction = _widget.createReaction(invalidate, onError: (e, _) {
}) as ReactionImpl;
super.mount(parent, newSlot);
}
代码语言:javascript复制 Reaction createReaction(
Function() onInvalidate, {
Function(Object, Reaction)? onError,
}) =>
ReactionImpl(
getContext(),
onInvalidate,
name: getName(),
onError: onError,
);
这里创建了一个 ReactImple
对象,当数据变化的时候,就会执行上面的 invallidate 方法,重建我们的 UI。
总结
到这里我们就比较完整的了解了flutter_mobx的核心流程。这里能看到,如果我们理清楚了数据的流向,找到了观察者和被观察者,其实大体流程就比较清晰了。虽然数据状态本身的处理细节比较复杂,但是我们其实也可以不做过多关心。了解flutter_mobx的大致实现对于我们在遇到具体问题的时候,可以提供一些有效的帮助。