навигация
- Учебные статьи по Flutter (1) — простое использование Dialog
- Исследование флаттера (2) - Эффект нажатия на ящик и водяной узор
предисловие
MobXЭто очень популярное функциональное реактивное программирование во внешнем интерфейсе, делающее состояние простым и расширяемым. За этим стоит философия:
Все, что получено из состояния приложения, должно быть получено автоматически.
Платформа MVVM на основе наблюдателя завершает двустороннюю привязку данных к пользовательскому интерфейсу. Google также выпустил фреймворк MVVM ViewModel с похожими идеями в 2017 году. MVVM — это среда обновления на основе данных, которая может легко разделять страницы и логику и очень популярна во внешнем интерфейсе. Поэтому MobX также выпустила версию dart для поддержки использования Flutter. Давайте начнем знакомить с MobX во Flutter.
использовать
отпустить первымОфициальный сайт, используя несколько шагов:
1. Сначала введем зависимости:
mobx: ^0.2.0
flutter_mobx: ^0.2.0
mobx_codegen: ^0.2.0
2. Добавьте магазин:
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 генерируется новый класс для достижения эффекта двухсторонней привязки, поэтому необходимо добавить в хранилище некоторые определения сгенерированного класса:
part 'settings_store.g.dart';
class SettingsStore = _SettingsStore with _$SettingsStore;
_$SettingsStore — это создаваемый класс, а SettingsStore — это новый класс, который сочетает в себе два хранилища. Следующие классы создаются автоматически:
// 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));
}
}
Для достижения вышеуказанного эффекта необходимо сделать несколько шагов:
-
Добавьте аннотацию @observable к данным, которые необходимо наблюдать, и добавьте аннотацию @action к методу, который должен выполнять операции.
-
Затем выполните
flutter packages pub run build_runner build
-
Вышеупомянутые классы будут сгенерированы автоматически, в частности, если вам нужно отслеживать изменения магазина в режиме реального времени, чтобы изменить только что сгенерированные классы в реальном времени, вам необходимо выполнить команду:
flutter packages pub run build_runner watch
, если операция не удалась, попробуйте следующую чистую команду:flutter packages pub run build_runner watch --delete-conflicting-outputs
3. Использовать в виджете:
Поместите слой виджета Observer на виджет, который должен отслеживать изменения данных,
_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}'),
)
));
}
После выполнения вышеуказанных шагов вы можете автоматически обновлять виджет, оперируя данными в магазине.
принцип
Я полагаю, что после прочтения приведенного выше использования читатели будут чувствовать себя озадаченными и волшебными. Не волнуйтесь, давайте перейдем к анализу принципа далее.
Сначала посмотрите на недавно сгенерированный код _$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 наблюдателя, видя это, я считаю, что умные читатели должны помнить о существовании наблюдателя. То есть мы используем виджет Наблюдателя, вложенный в виджет наблюдаемых данных. Исходный код выглядит следующим образом:
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. Это должно быть ясно, когда вы видите его здесь. На самом деле, мы помещаем родительский виджет поверх нашего виджета, и он имеет тип StatefulWidget. Таким образом, пока родитель обновляется виджет, так же наш виджет можно обновить.
В процессе здания вы можете видеть, что метод трека вызывается. Отслеживание исходного кода, вы можете обнаружить, что входящий метод называется первым (это соответствует конструкции нашего виджета), а затем наблюдается в очередь наблюдателя:
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, то можно обнаружить, что наконец вызывается метод 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()
Это метод, который передается при формировании наблюдателя:
void _invalidate() => setState(noOp);
static void noOp() {}
Увидев это, он фактически вышел на первый план, то есть виджет обновляется вызовом setState.
Пополнить
Читатель спросил,Flutter не поддерживает отражение, как mobx справляется с аннотациями. По этой причине я специально просмотрел исходный код mobx и обнаружил, что mobx — это класс TypeChecker, который использует статическую проверку типов dart.
/// An abstraction around doing static type checking at compile/build time.
abstract class TypeChecker {
const TypeChecker._();
}
Мы знаем, что для флаттера в режиме отладки он компилируется на основе JIT, поэтому вы можете получить аннотационную информацию во время выполнения, а в продукте В режиме компилируется на основе AOT, поэтому информацию о времени выполнения получить нельзя. Таким образом, в версии mobx для dart он фактически генерирует код шаблона для поддержки в режиме отладки Таким образом, mobx также может поддерживать функцию отражения при моделировании среды выполнения.
Суммировать
Для Mobx суть в том, чтобы поместить родительский виджет на виджет, который использует наблюдаемые данные, а родительский виджет — это StatefulWidget. Затем через режим наблюдателя, когда обнаруживается, что данные изменяются, наблюдатель уведомляется, а затем наблюдатель вызывает setState и обновляет наблюдателя, чтобы, наконец, добиться эффекта обновления дочернего виджета.
склад
нажмитеflutter_demo, см. полный код.