以下截取自官方解說
MobX 的核心為 Observable, Action, Reaction
Observable 代表的是 App 的 State
,讓 Observers 觀察並得到更新。
簡單的 Observable 可以定義如下:
import 'package:mobx/mobx.dart';
final counter = Observable(0);
複雜的 Observable 可以藉由 mobx_codegen package,來定義如下:
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
App 的 state
又可以分為 core-state
和 derived-state
,例如姓名中的 firstname
和 lastname
屬於一個人姓名的 core-state,而現在有一個 derived-state 稱為 fullname
,即是透過 firstname
和 lastname
這兩個 core-state 來產生。
import 'package:mobx/mobx.dart';
part 'contact.g.dart';
class Contact = ContactBase with _$Contact;
abstract class ContactBase with Store {
@observable
String firstName;
@observable
String lastName;
@computed
String get fullName => '$firstName, $lastName';
}
core-state => @observable
derived-state => @computed
fullname 將會隨著 firstname & lastname 的改變而改變。
Action 則為改變 Observable 的方法,比起直接賦值給 Observable,建立一個帶有語義的 Action 更有意義。
以下為沒有使用 mobx_codegen 的寫法:
final counter = Observable(0);
final increment = Action((){
counter.value++;
});
再來是有使用 mobx_codegen 的寫法:
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
mobX.dart 還支援非同步的 Action 而且不用被 runInAction 綁定:
@observable
String stuff = '';
@observable
loading = false;
@action
Future<void> loadStuff() async {
loading = true; //This notifies observers
stuff = await fetchStuff();
loading = false; //This also notifies observers
}
Reaction 是 MobX 的最後一角,即為這整個 react-system 的 Observer,當他關注的 Observable 被改變時,將會被通知。
Reaction 有多種不同的版本,但都會返回一個 ReactionDisposer
用來 dispose 該 reaction。
有一個很棒的 feature 是:reaction 訂閱 observable 不需要什麼繁複的綁定
簡單的在 reaction 中 reading observable 就足夠了!
reaction 將直接執行一次,並且之後在 fn
裡的 observables 改變時也會立刻執行。
import 'package:mobx/mobx.dart';
String greeting = Observable('Hello World');
final dispose = autorun((_) => print(greeting.value));
greeting.value = 'Hello MobX';
// Done with the autorun()
dispose();
// Prints:
// Hello World
// Hello MobX
只觀察在 predicate()
中的 observables,等到他們改變時,便執行 effect()
裡的動作。
import 'package:mobx/mobx.dart';
String greeting = Observable('Hello World');
final dispose = reaction((_) => greeting.value, (msg) => print(msg));
greeting.value = 'Hello MobX'; // Cause a change
// Done with the reaction()
dispose();
// Prints:
// Hello MobX
只觀察在 predicate()
中的 observables,等到他們改變且為 true
時,便執行 effect()
裡的動作。
執行完後會自動 dispose,可以將他視為 一次性 的 reaction
。 也可以在觸發之前就先 dispose 他。
import 'package:mobx/mobx.dart';
String greeting = Observable('Hello World');
final dispose = when((_) => greeting.value == 'Hello MobX', () => print('Someone greeted MobX'));
greeting.value = 'Hello MobX'; // Causes a change, runs effect and disposes
// Prints:
// Someone greeted MobX
相當於 when
但是是回傳 Future
。是一個等待 predicate()
變成 true
的好方法。
final completed = Observable(false);
void waitForCompletion() async {
await asyncWhen(() => _completed.value == true);
print('Completed');
}
最能更視覺化 reaction 的即是 UI,所以 flutter_mobx package 提供了 Observer
widget 作為 Observer 角色訂閱 Observable 的每個改變,並在 builder 中 rebuild 與 rerender 。
底下是一個使用 Observer 的 Counter example:
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);
@override
_CounterExampleState createState() => _CounterExampleState();
}
class _CounterExampleState extends State<CounterExample> {
final _counter = Counter();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}