MobX是前端一个很流行的函数响应式编程,让状态变得简单可扩展。背后的哲学是:前端
任何源自应用状态的东西都应该自动地得到react
基于观察者的MVVM框架完成了数据到UI的双向绑定。Google2017年也发布了相似思想的MVVM框架ViewModel。MVVM是数据驱动更新的框架,能够很方便地把页面和逻辑抽开,在前端很受欢迎。因此MobX也出了dart的版本用来支持Flutter的使用。下面咱们就开始动手在Flutter引入MobX。git
先放出官网,使用分几步走:github
mobx: ^0.2.0
flutter_mobx: ^0.2.0
mobx_codegen: ^0.2.0
复制代码
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 build
async
就会自动生成上述的类,特别的是,若是须要实时跟踪store的变化从而实时改变新生成的类,须要执行一个命令:ide
flutter packages pub run build_runner watch
, 若是操做失败了,能够尝试下面的clean命令:
flutter packages pub run build_runner watch --delete-conflicting-outputs
在须要观察数据变化的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,查看完整代码。