前言
MobX 的设计似乎很神奇,感觉使用了一个 Observer 后就能自动跟踪状态对象的变化,实现响应式刷新。这个具体是如何实现的呢?我们来从源码梳理一下。
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');
@overrideint get praiseCount { _$praiseCountAtom.reportRead(); return super.praiseCount;}
@overrideset 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 实现的细节还有更多,有兴趣的同学也可以深入了解其设计思想。
评论