Flutter學習篇(三)—— MobX的使用和原理

導航

前言

MobX是前端一個很流行的函數響應式編程,讓狀態變得簡單可擴展。背後的哲學是:前端

任何源自應用狀態的東西都應該自動地得到react

基於觀察者的MVVM框架完成了數據到UI的雙向綁定。Google2017年也發佈了相似思想的MVVM框架ViewModel。MVVM是數據驅動更新的框架,能夠很方便地把頁面和邏輯抽開,在前端很受歡迎。因此MobX也出了dart的版本用來支持Flutter的使用。下面咱們就開始動手在Flutter引入MobX。git

使用

先放出官網,使用分幾步走:github

1. 首先,引入依賴:

 mobx: ^0.2.0
 flutter_mobx: ^0.2.0
 mobx_codegen: ^0.2.0

複製代碼

2. 添加一個store:

import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';

// 自動生成的類
part 'settings_store.g.dart';
class SettingsStore = _SettingsStore with _$SettingsStore; abstract class _SettingsStore implements Store {
  var key = {
    "showPage":"showPage",
  };

  @observable
  String showPage = "";

  @action
  getPrefsData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    showPage = prefs.get(key["showPage"]) ?? "首頁";
  }

  @action
  saveShowPage(String showPage) async {
    if(showPage == null) {
      return;
    }
    this.showPage = showPage;
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(key["showPage"], showPage);
  }


}

複製代碼

對於dart版本的mobx,是經過生成新的類來實現雙向綁定的效果,因此須要在store裏面加上生成類的一些定義:express

part 'settings_store.g.dart';
class SettingsStore = _SettingsStore with _$SettingsStore; 複製代碼

_$SettingsStore是待生成的類,SettingsStore則是混合了兩個store的新類。以下是自動生成的類:編程

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'settings_store.dart';

// **************************************************************************
// StoreGenerator
// **************************************************************************

// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars

mixin _$SettingsStore on _SettingsStore, Store {
  final _$showPageAtom = Atom(name: '_SettingsStore.showPage');

  @override
  String get showPage {
    _$showPageAtom.reportObserved();
    return super.showPage;
  }

  @override
  set showPage(String value) {
    _$showPageAtom.context.checkIfStateModificationsAreAllowed(_$showPageAtom);
    super.showPage = value;
    _$showPageAtom.reportChanged();
  }

  final _$getPrefsDataAsyncAction = AsyncAction('getPrefsData');

  @override
  Future getPrefsData() {
    return _$getPrefsDataAsyncAction.run(() => super.getPrefsData());
  }

  final _$saveShowPageAsyncAction = AsyncAction('saveShowPage');

  @override
  Future saveShowPage(String showPage) {
    return _$saveShowPageAsyncAction.run(() => super.saveShowPage(showPage));
  }
}

複製代碼

要實現上面的效果還須要分幾步走:markdown

  • 在須要被觀察的數據增長@observable註解,須要執行操做的方法增長@action註解,框架

  • 接着執行flutter packages pub run build_runner buildasync

  • 就會自動生成上述的類,特別的是,若是須要實時跟蹤store的變化從而實時改變新生成的類,須要執行一個命令:ide

    flutter packages pub run build_runner watch , 若是操做失敗了,能夠嘗試下面的clean命令:

    flutter packages pub run build_runner watch --delete-conflicting-outputs

3. 在widget中使用:

在須要觀察數據變化的widget套上一層Observer widget,

_buildShowPageLine(BuildContext context) {
    return GestureDetector(
        onTap: () {
          showDialog<String>(
              context: context,
              builder: (context) {
                String selectValue = '${settingsStore.showPage}';
                List<String> valueList = ["首頁", "生活"];
                return RadioAlertDialog(title: "選擇展現頁面",
                    selectValue: selectValue,
                    valueList: valueList);
              }).then((value) {
                print(value);
                settingsStore.saveShowPage(value);
          });
        },
        
        // 在須要觀察變化的widget套上一層Observer widget,
        child: Observer(
            builder: (_) => ListTile(
                  title: Common.primaryTitle(content: "默認展現頁面"),
                  subtitle: Common.primarySubTitle(content: '${settingsStore.showPage}'),
                )
        ));
  }

複製代碼

完成上述步驟就能夠經過對store的數據進行操做,從而自動刷新widget。

原理

看完上述的使用以後,相信讀者會感到又疑惑又神奇。別急,接下來就進入原理的剖析。
首先看到新生成的代碼_$SettingsStore,其中有幾處關鍵的插樁代碼,

@override
  String get showPage {
    _$showPageAtom.reportObserved();
    return super.showPage;
  }

  @override
  set showPage(String value) {
    _$showPageAtom.context.checkIfStateModificationsAreAllowed(_$showPageAtom);
    super.showPage = value;
    _$showPageAtom.reportChanged();
  }

複製代碼

能夠看到在獲取變量時,會調用dart reportObserved(), 設置變量會調用dart reportChanged, 從名字就能夠看出獲取變量就是將變量上報,變爲被觀察的狀態,設置變量其實就是上報數據變化,進行通知。
咱們先看看reportObserved()作了什麼,

// atom能夠理解爲對應的被觀察變量的封裝
  void _reportObserved(Atom atom) {
    final derivation = _state.trackingDerivation;

    if (derivation != null) {
      derivation._newObservables.add(atom);
      if (!atom._isBeingObserved) {
        atom
          .._isBeingObserved = true
          .._notifyOnBecomeObserved();
      }
    }
  }

複製代碼

能夠看出核心就是把當前的變量加入被觀察的隊列中去。

reportChanged作的是啥呢,

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;
    }
  }


複製代碼

關鍵的代碼是

if (observer._dependenciesState == DerivationState.upToDate) {
    observer._onBecomeStale();
 }
複製代碼

當數據須要更新的時候,調用觀察者的_onBecomeStale方法,看到這裏,相信聰明的讀者應該會記起觀察者的存在了。 那就是咱們用了被觀察數據的widget上面套着的Observer的widget。源碼以下:

library flutter_mobx;

// ignore_for_file:implementation_imports
import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx/src/core.dart' show ReactionImpl;

/// Observer observes the observables used in the `builder` function and rebuilds the Widget
/// whenever any of them change. There is no need to do any other wiring besides simply referencing
/// the required observables.
///
/// Internally, [Observer] uses a `Reaction` around the `builder` function. If your `builder` function does not contain
/// any observables, [Observer] will throw an [AssertionError]. This is a debug-time hint to let you know that you are not observing any observables.
class Observer extends StatefulWidget {
  /// Returns a widget that rebuilds every time an observable referenced in the
  /// [builder] function is altered.
  ///
  /// The [builder] argument must not be null. Use the [context] to specify a ReactiveContext other than the `mainContext`.
  /// Normally there is no need to change this. [name] can be used to give a debug-friendly identifier.
  const Observer({@required this.builder, Key key, this.context, this.name})
      : assert(builder != null),
        super(key: key);

  final String name;
  final ReactiveContext context;
  final WidgetBuilder builder;

  @visibleForTesting
  Reaction createReaction(Function() onInvalidate) {
    final ctx = context ?? mainContext;
    return ReactionImpl(ctx, onInvalidate,
        name: name ?? 'Observer@${ctx.nextId}');
  }

  @override
  State<Observer> createState() => _ObserverState();

  void log(String msg) {
    debugPrint(msg);
  }
}

class _ObserverState extends State<Observer> {
  ReactionImpl _reaction;

  @override
  void initState() {
    super.initState();

    _reaction = widget.createReaction(_invalidate);
  }

  void _invalidate() => setState(noOp);

  static void noOp() {}

  @override
  Widget build(BuildContext context) {
    Widget built;
    dynamic error;

    _reaction.track(() {
      try {
        built = widget.builder(context);
      } on Object catch (ex) {
        error = ex;
      }
    });

    if (!_reaction.hasObservables) {
      widget.log(
          'There are no observables detected in the builder function for ${_reaction.name}');
    }

    if (error != null) {
      throw error;
    }
    return built;
  }

  @override
  void dispose() {
    _reaction.dispose();
    super.dispose();
  }
}


複製代碼

猜猜咱們看到了什麼, Observer繼承自StatefulWidget,看到這裏應該就豁然開朗了吧,其實就是在咱們的widget上面套了一個父的widget,而且是StatefulWidget類型的,這樣一來,只要更新了父widget,一樣的咱們的widget也就能夠進行更新了。
在build的過程,能夠看到調用了track方法,跟蹤源碼能夠發現就是先調用了傳入的方法(這裏對應的是咱們widget的構建),而後就是把Observer插入觀察者隊列:

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
  }

複製代碼

接着咱們須要找出觀察者的_onBecomeStale方法,若是跟蹤_onBecomeStale方法,能夠發現最終調用的是reaction的run方法:

@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();
  }

複製代碼

其中的_onInvalidate()就是在observer構成的時候傳入的方法:

void _invalidate() => setState(noOp);

  static void noOp() {}

複製代碼

看到這裏,其實已經水落石出了,就是經過調用的setState從而刷新了widget。

補充

有讀者問到,flutter不支持反射,那mobx是怎麼去處理註解的。爲此我特地去翻了mobx的源碼,發現mobx是使用了dart的靜態類型檢查的類TypeChecker,

/// An abstraction around doing static type checking at compile/build time.
abstract class TypeChecker {
  const TypeChecker._();
}
複製代碼

咱們知道,對於flutter,debug模式下,是基於JIT去編譯的,因此能夠在runtime的時候獲得註解的信息,而在product 模式下,則是基於AOT進行編譯,因此沒法獲得runtime的信息。因此在dart版本的mobx,它實際上是在debug的模式下去生成一份模版代碼進行支持,經過這種方式,mobx就可以模擬runtime的時候也支持reflection的特性。

總結

對於Mobx,本質就是在使用了被觀察數據的widget上面套了一個父的widget,而這個父的widget是一個StatefulWidget。 而後經過觀察者模式,發現數據更改時,通知觀察者,而後觀察者調用了setState了,更新了Observer,從而最後達到刷新子widget的效果。

倉庫

點擊flutter_demo,查看完整代碼。

相關文章
相關標籤/搜索