带你走进Flutter_Mobx

2022-05-10 20:49:56 浏览数 (1)

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 对象:

代码语言:javascript复制
 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中,会检查是否有在计算中去观察变量的行为,有的话会报错。

startActionendAction则是一对操作:

代码语言:javascript复制
// 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:

代码语言:javascript复制
 void reportChanged() {
    _context
      ..startBatch()
      ..propagateChanged(this)
      ..endBatch();
  }

这里会执行 endBatch:

代码语言:javascript复制
 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:

代码语言:javascript复制
 
 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:

代码语言:javascript复制
 @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方法的调用开始:

代码语言:javascript复制

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

代码语言:javascript复制
 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:

代码语言:javascript复制
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的大致实现对于我们在遇到具体问题的时候,可以提供一些有效的帮助。

0 人点赞