写点什么

从源码解析 MobX 响应式刷新机制

作者:岛上码农
  • 2022 年 6 月 12 日
  • 本文字数:3473 字

    阅读完需:约 11 分钟

从源码解析 MobX 响应式刷新机制

前言

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

这是一个用于 Widgetmixin,主要的用途是使用 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 重载了 Elemenntmount 方法,在mount 里面创建了 reaction,其中响应的方法是invalidate,而 invalidate 方法实际上就是markNeedsBuild方法。也就是说状态数据发生改变的时候,实际上会通过 reaction 来调用markNeedsBuild通知Element刷新,这个方法实际上会触发 Widgetbuild 方法。

在这个 mixin 里面还重载了 build方。这里调用了reactiontrack方法。一层层跟踪下去,实际上是这里主要的目的是将observer对象和其依赖(其实也就是Observerbuilder返回的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 方法,这个方法正是在ObserverElementMixincreateReaction的时候传进来的,这个方法会触发 Widgetbuild


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 完成无感知响应的方式如下:


  • 控制渲染的 ElementStatelessObserverElement,该类在mount 阶段通过createReaction注册了 reaction

  • StatelessObserverElementbuild 方法中reactionobservable进行绑定。

  • Observer 中读取状态对象属性时,会调用到其 get 方法,该方法会将状态对象属性与对应的 Observer组件 进行绑定。

  • 当状态对象的属性被 set 更改的时候,会调度到该属性绑定的reaction,执行_onInvalidate方法来进行刷新,从而实现了响应式的无感知刷新。


当然这只是我们简单的分析,实际 MobX 实现的细节还有更多,有兴趣的同学也可以深入了解其设计思想。


发布于: 2022 年 06 月 12 日阅读数: 15
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
从源码解析 MobX 响应式刷新机制_flutter_岛上码农_InfoQ写作社区