What is Mobx

Cn.mobx.js.org/ Use the left-hand address to learn more about Mobx.

Mobx’s role can be summed up in one sentence: Enhanced state management in the Dart application with transparent functional responsive programming, the Dart version of the well-known mobx.js in the front end.

Three important Mobx concepts

  • Observables: Indicates the state of responsiveness, also known as Observables. State refers to the state or data in an application. Responsiveness is the perceived, observable change in data, which is the observer pattern that we are often exposed to.
  • Actions: Actions are a series of Actions that cause a change in state.
  • Reactions: State observers who receive notifications when their state changes.

The specific use

In the case of a Counter, a simple Counter can be represented as an observable numerical state, and the Counter is represented as a Counter object.

part 'counter.g.dart'; class Counter = CounterBase with _$Counter; abstract class CounterBase with Store { @observable int value = 0; @action void increment() { value++; }}Copy the code

Here, Mobx uses the builder_Runner library to generate code for the corresponding _$Counter class. The values in the counter need to be reflected to the UI via widgets in Flutter_Mobx, using an Observer to wrap the components that need to be observed:

final counter = Counter(); // Instantiate the store

Observer(
    builder: (_) => Text('${counter.value}',
    style: Theme.of(context).textTheme.display1,
    ),
),
Copy the code

We use a Widget called Observer where the Builder method passes the value of the Counter Observable as a property to the Text.

The official advice

The official recommendation is to combine widget-store-service. Widget: visual representation of state Store: processing state Service: logical operations, including complex logic, network requests, local database storage, etc.

  1. The UI layer should combine StatelessWidgets and observers as much as possible to reduce the number of times widgets are rebuilt and improve performance.
  2. The @ Observable object placed in the Store can’t be reflected at runtime by Dart, so we need to declare the complex object as an Observable ourselves. Otherwise it won’t take effect. When you need to deal with derived states, use computed instead.

Questions about how different pages hold Store objects

The simplest is to write a store for singletons directly, but the drawback of singletons is obvious. What we need is that the object is the same in each page. Beyond that, the object can be destroyed, or another object is used. Use the Provider framework for dependency injection. See pub.dev/packages/pr… .

The principle of

First, in the Counter example above, you see the newly generated class _$Counter, with the following code in part:

@override
  int get value {
    _$valueAtom.reportRead();
    return super.value;
  }

  @override
  set value(int value) {
    _$valueAtom.reportWrite(value, super.value, () {
      super.value = value;
    });
  }
Copy the code

As you can see, reportRead() is called when fetching the variable, and reportWrite is called when setting the variable. Let’s look at what reportRead() does,

void reportRead() { context.enforceReadPolicy(this); //1 reportObserved(); / / 2}Copy the code

The first line of code is to ensure that observations are read inside Actions and Reactions. The focus is on the reportObserved method called on the second line, which has the following code:

// Atom can be understood as the encapsulation of the corresponding observed object void _reportObserved(atom atom) {final derivation = _state.trackingderivation; if (derivation ! = null) { derivation._newObservables.add(atom); if (! atom._isBeingObserved) { atom .. _isBeingObserved = true .. _notifyOnBecomeObserved(); }}}Copy the code

As you can see, all this code does is add the current variable to the observed queue, and if the variable is not observed, set it to the observed state. Here’s what reportWrite does:

void reportWrite<T>(T newValue, T oldValue, void Function() setNewValue) {
    context.spyReport(ObservableValueSpyEvent(this,
        newValue: newValue, oldValue: oldValue, name: name));

    // ignore: cascade_invocations
    context.conditionallyRunInAction(() {
      setNewValue();
      reportChanged();
    }, this, name: '${name}_set');

    // ignore: cascade_invocations
    context.spyReport(EndedSpyEvent(type: 'observable', name: name));
  }
Copy the code

At its core, the reportChanged method is executed,

void reportChanged() { _context .. startBatch() .. propagateChanged(this) .. endBatch(); }Copy the code

Take a look at what’s done in propagateChanged() :

void propagateChanged(Atom atom) { if (atom._lowestObserverState == DerivationState.stale) { return; } atom._lowestObserverState = DerivationState.stale; for (final observer in atom._observers) { if (observer._dependenciesState == DerivationState.upToDate) { observer._onBecomeStale(); } observer._dependenciesstate = derivationstate.stale; }}Copy the code

Call the observer’s _onBecomeState method when the data needs to be updated. Look at observable.dart and track it down (too complicated to cover here), and you’ll see that reaction’s run method is called,

for (final reaction in remainingReactions) {
        reaction._run();
      }
Copy the code

As follows:


  @override
  void _run() {
    if (_isDisposed) {
      return;
    }

    _context.startBatch();

    _isScheduled = false;

    if (_context._shouldCompute(this)) {
      try {
        _onInvalidate();
      } on Object catch (e) {
        // Note: "on Object" accounts for both Error and Exception
        _errorValue = MobXCaughtException(e);
        _reportException(e);
      }
    }

    _context.endBatch();
  }
Copy the code

Where _onInvalidate() is the method passed in when an observer is formed:

  void _invalidate() => setState(noOp);

  static void noOp() {}
Copy the code

Looking at this, we see that the widget is refreshed by calling setState.

The resources

  1. Juejin. Cn/post / 684490…
  2. Takeroro. Making. IO / 2020/06/30 /…