MobX 的设计似乎很神奇,感觉使用了一个 Observer 后就能自动跟踪状态对象的变化,实现响应式刷新。这个具体是如何实现的呢?我们来从源码梳理一下。
Observer 类的类关系图如下图所示。
这里面有几个关键的类,我们一一进行介绍。
StatelessObserverWidget
abstract class StatelessObserverWidget extends StatelessWidget
with ObserverWidgetMixin {
/// Initializes [key], [context] and [name] for subclasses.
const StatelessObserverWidget(
{Key? key, ReactiveContext? context, String? name})
: _name = name,
_context = context,
super(key: key);
final String? _name;
final ReactiveContext? _context;
@override
String getName() => _name ?? '$this';
@override
ReactiveContext getContext() => _context ?? super.getContext();
@override
StatelessObserverElement createElement() => StatelessObserverElement(this);
}
这里的 createElement
覆盖了 StatelessWidget
的方法,返回的是一个StatelessObserverElement
对象,目的是用于控制Element
的刷新。
ObserverWidgetMixin
这是一个用于 Widget
的mixin
,主要的用途是使用 createReaction
方法创建 reaction
,以便在状态改变的时候调用对应的方法。这个 createReaction
实际是在ObserverElementMixin
中被调用的。
mixin ObserverWidgetMixin on Widget {
String getName();
ReactiveContext getContext() => mainContext;
@visibleForTesting
Reaction createReaction(
Function() onInvalidate, {
Function(Object, Reaction)? onError,
}) =>
ReactionImpl(
getContext(),
onInvalidate,
name: getName(),
onError: onError,
);
void log(String msg) {
debugPrint(msg);
}
}
StatelessObserverElement
StatelessObserverElement
这个其实就是一个特殊的 StatelessElement
。这个类仅仅是混入了ObserverElementMixin
。所有特殊的业务都是在ObserverElementMixin
中实现的,我们来看看ObserverElementMixin
的源码。
mixin ObserverElementMixin on ComponentElement {
ReactionImpl get reaction => _reaction;
late ReactionImpl _reaction;
// Not using the original `widget` getter as it would otherwise make the mixin
// impossible to use
ObserverWidgetMixin get _widget => widget as ObserverWidgetMixin;
@override
void mount(Element? parent, dynamic newSlot) {
_reaction = _widget.createReaction(invalidate, onError: (e, _) {
FlutterError.reportError(FlutterErrorDetails(
library: 'flutter_mobx',
exception: e,
stack: e is Error ? e.stackTrace : null,
));
}) as ReactionImpl;
super.mount(parent, newSlot);
}
void invalidate() => markNeedsBuild();
@override
Widget build() {
late Widget built;
reaction.track(() {
built = super.build();
});
if (!reaction.hasObservables) {
_widget.log(
'No observables detected in the build method of ${reaction.name}',
);
}
return built;
}
@override
void unmount() {
reaction.dispose();
super.unmount();
}
}
可以看到,这个 mixin
重载了 Elemennt
的 mount
方法,在mount
里面创建了 reaction
,其中响应的方法是invalidate
,而 invalidate
方法实际上就是markNeedsBuild
方法。也就是说状态数据发生改变的时候,实际上会通过 reaction
来调用markNeedsBuild
通知Element
刷新,这个方法实际上会触发 Widget
的 build
方法。
在这个 mixin
里面还重载了 build
方。这里调用了reaction
的track
方法。一层层跟踪下去,实际上是这里主要的目的是将observer
对象和其依赖(其实也就是Observer
的 builder
返回的widget
)进行绑定。
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;
}
}
}
// Remove previous observables
for (final ob in staleObservables) {
ob._removeObserver(derivation);
}
if (lowestNewDerivationState != DerivationState.upToDate) {
derivation
.._dependenciesState = lowestNewDerivationState
.._onBecomeStale();
}
derivation
.._observables = derivation._newObservables!
.._newObservables = {}; // No need for newObservables beyond this point
}
这条线基本上就理完了,那具体又是怎么精准跟踪的呢?我们来看看 MobX 生成的那部分代码。
生成的代码里面,带有@observable
注解的成员生成代码如下:
final _$praiseCountAtom = Atom(name: 'ShareStoreBase.praiseCount');
@override
int get praiseCount {
_$praiseCountAtom.reportRead();
return super.praiseCount;
}
@override
set praiseCount(int value) {
_$praiseCountAtom.reportWrite(value, super.praiseCount, () {
super.praiseCount = value;
});
}
这里面关键在于 get
方法中调用了Atom
类的reportRead
方法。实际上最终调用的是_reportObserved
方法。这个方法其实就是将之前Observer
绑定的依赖和对应的状态对象属性关联起来。因此,才能够实现状态对象的某个属性更新时,只更新依赖该属性的组件,实现精准更新。
void _reportObserved(Atom atom) {
final derivation = _state.trackingDerivation;
if (derivation != null) {
derivation._newObservables!.add(atom);
if (!atom._isBeingObserved) {
atom
.._isBeingObserved = true
.._notifyOnBecomeObserved();
}
}
}
接下来来看 set
方法。set
方法其实就是改变了状态对象的属性,这里调用了Atom
类的reportWrite
方法。这会触发下面的reaction
调度方法:
void schedule() {
if (_isScheduled) {
return;
}
_isScheduled = true;
_context
..addPendingReaction(this)
..runReactions();
}
这个调度方法最终会执行reaction
的_run
方法,这里面我们看到了执行了_onInvalidate
方法,这个方法正是在ObserverElementMixin
中 createReaction
的时候传进来的,这个方法会触发 Widget
的 build
。
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();
}
由此我们得知了状态对象改变的时候是如何进行刷新的。
整个过程我们跟踪一下,实际 MobX 完成无感知响应的方式如下:
Element
是StatelessObserverElement
,该类在mount
阶段通过createReaction
注册了 reaction
。StatelessObserverElement
在 build
方法中reaction
和observable
进行绑定。Observer
中读取状态对象属性时,会调用到其 get
方法,该方法会将状态对象属性与对应的 Observer
组件 进行绑定。set
更改的时候,会调度到该属性绑定的reaction
,执行_onInvalidate
方法来进行刷新,从而实现了响应式的无感知刷新。当然这只是我们简单的分析,实际 MobX 实现的细节还有更多,有兴趣的同学也可以深入了解其设计思想。
领取专属 10元无门槛券
私享最新 技术干货