Loading...
墨滴

岛上码农@公众号同名

2021/12/02  阅读:44  主题:橙心

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

前言

MobX 的设计似乎很神奇,感觉使用了一个 Observer 后就能自动跟踪状态对象的变化,实现响应式刷新。这个具体是如何实现的呢?我们来从源码梳理一下。

Observer 类

Observer 类的类关系图如下图所示。

MobX 核心类关系
MobX 核心类关系

这里面有几个关键的类,我们一一进行介绍。

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,关于 Element的介绍可以看之前本专栏关于渲染机制的篇章:Flutter 入门与实战(三十九):渲染模式详解。这个类仅仅是混入了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 方法。关于markNeedsBuild这个方法本专栏在之前的篇章都有介绍:

在这个 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');

@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 方法,这个方法正是在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实现的细节还有更多,有兴趣的同学也可以深入了解其设计思想。

岛上码农@公众号同名

2021/12/02  阅读:44  主题:橙心

作者介绍

岛上码农@公众号同名