這是我參與8月更文挑戰的第29天,活動詳情檢視:8月更文挑戰
本篇主要內容翻譯自MobX 的官方 Readme 文件:github.com/mobxjs/mobx…
MobX 簡介
MobX 是一個狀態管理框架,它可以輕鬆地將應用的響應式資料和 UI 繫結起來。這個繫結是完全自動的,而且不會感覺到彆扭。MobX 使得應用開發者只需要關注 UI 需要消費哪些響應式資料,而無需關注如何保持二者同步。
MobX 的實現並沒有什麼神奇之處,但是使用了一些技巧來包裹消費(observables
)的物件,哪裡需要這些物件(reactions
),並且自動跟蹤這些物件。一旦observables
物件發生了改變,所有 reactions
會被重新執行。有趣的是,這些reactions
可以是來自例如控制檯日誌、UI 所需的網路介面資料等任何物件。
注:MobX原先是JavaScript 的一個高效的狀態管理庫,Dart版本頁試圖帶來同樣的效果。關於 JavaScript 版本,可以到npm上檢視:JavaScript版本MobX。
核心元素
MobX 的核心要素有三個:、Observables
、Actions
、和 Reactions
,如下圖所示。接下來以簡單的 Counter
計數器應用來介紹各個元素的使用。
Observables
Observables
代表著應用的響應式狀態,它們可以是簡單物件到複雜的物件樹。如果定義應用的狀態為一棵 observables
樹,那麼可以向消費狀態的 UI(或應用中的其他觀察者)暴露一棵響應式的狀態樹(reactive-state-tree)。對於計數器應用,可以通過下面的方式定義:
import 'package:mobx/mobx.dart';
final counter = Observable(0);
複製程式碼
對於複雜的observables
,例如類,也是一樣。
class Counter {
Counter() {
increment = Action(_increment);
}
final _value = Observable(0);
int get value => _value.value;
set value(int newValue) => _value.value = newValue;
Action increment;
void _increment() {
_value.value++;
}
}
複製程式碼
這些程式碼看起來有點重複。因此可以使用MobX 程式碼生成器來簡化編寫(*.g.dart
檔案會由程式碼生成器自動生成)。使用程式碼生成器,需要在開發依賴中加入builder_runner
和 mobx_codegen
外掛,並在程式碼目錄下執行命令生成對應的*.g.dart
檔案。
flutter packages pub run build_runner build
複製程式碼
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++;
}
}
複製程式碼
需要注意的是,需要使用註解來標記類的哪些屬性是 observable
屬性。這些註解是在 MobX 的程式碼生成器中定義的。如果想減少程式碼量,可以將@observable
註解換成@readonly
註解。更換後,私有屬性將只提供 get 方法,而不會提供set
方法。並且這樣狀態的使用者也無法更改他們的值。
對於應用狀態由核心狀態和派生狀態組成的情況,核心狀態是指處理的業務領域固有的狀態。例如,如果有個 Contact
實體類,其中 firstName
和 lastName
組成了 Contact
的核心狀態。然而,fullName
是一個派生狀態,通過 firstName
和 lastName
組合得到。對於這種情況,依賴於核心狀態或其他派生狀態的稱之為Computed Observables
(有點類似 Vue 的計算屬性)。這種屬性也會在其依賴的狀態物件改變時自動保持同步。這類派生的物件使用@computed
註解即可。
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';
}
複製程式碼
Actions
Actions
定義瞭如何改變 observables
物件。相比直接更改,actions
讓更改操作更有語義學的意義(更易於理解和維護)。例如,相比如直接使用 value++
,呼叫一個 increment()
動作將攜帶更多意義。除此之外,actions
能夠分批次處理通知,以確保改變只有在完成之後才會被通知。從而使得觀察者是基於 action
的完成這一原子操作通知的。
注意,actions
是可以巢狀的,這種情況下,只有 最頂層的 action
完成後才會發出通知。
final counter = Observable(0);
final increment = Action((){
counter.value++;
});
複製程式碼
在類裡面使用 actions
的時候,可以利用註解@action
來簡化程式碼。
//...
abstract class CounterBase with Store {
//...
@action
void increment() {
value++;
}
}
複製程式碼
對於非同步操作,MobX 會自動處理,而無需使用 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
}
複製程式碼
Reactions
Reactions
是Mobx三元素的observables
、actions
和reactions
的完結部分。Reactions
是響應式系統中的觀察者,一旦跟蹤的 observable
物件發生改變後就會被通知到。Reactions
有幾種不同的方式。所有方式都會返回一個 ReactionDisposer
方法,呼叫該方法可以銷燬該 reaction
。
Reactions
的一個典型特性是自動跟蹤所有的observable
物件,而無需顯式地與其繫結。在 reaction
中讀取observables
時就已經自動跟蹤該物件了。
Reactions
方式一,autorun
方法。
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();
// 列印結果:
// Hello World
// Hello MobX
複製程式碼
Reactions
方式二,reaction
方法:
ReactionDisposer reaction<T>(T Function(Reaction) predicate, void Function(T) effect)
複製程式碼
在 predicate
方法中監測 observables
物件,然後當predicate
返回不同的值時會執行 effect
方法。且只有 predicate
中的observables
物件會被跟蹤。
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();
// 列印結果:
// Hello MobX
複製程式碼
- Reactions 方式三,when 方法:
ReactionDisposer when(bool Function(Reaction) predicate, void Function() effect)
複製程式碼
當predicate
方法返回 true
時才執行 effect
方法。當 effect
方法執行後,將會自動銷燬。因此可以當作是一次性的 reaction
。當然也可以提前手動銷燬該 reaction
。
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
複製程式碼
- Reactions 方式四,Future 非同步方法:
Future<void> asyncWhen(bool Function(Reaction) predicate)
複製程式碼
和 when 方式類似,只是返回的結果是一個 Future 物件——在 predicate 方法返回 true 的時候完成。這對於等待 predicate 方法的返回值為 true 時很方便。
final completed = Observable(false);
void waitForCompletion() async {
await asyncWhen(() => _completed.value == true);
print('Completed');
}
複製程式碼
Observer
對於 app 而言,UI 是使用最多的視覺化 reactions
之一。Observer
元件(在 flutter_mobx
外掛中定義),在它的 build
方法中,為 observables
物件提供了一個顆粒度可控的觀察者。當observables
發生改變的時候,Observer
將重建並重新繪製。
總結
簡單的計數器程式碼已經上傳至:MobX 狀態管理原始碼。從官方的介紹可以看到,MobX
的使用方面還是挺簡潔的,而且有了程式碼生成器加持後,狀態管理部分的程式碼相對會容易編寫很多。比如 Actions
無需自己編寫,狀態屬性也只需要加註解就行。對於無法生成的部分,其實可以使用 VSCode
的程式碼模板來完成,這樣整個程式碼的編寫效率就會很高了。
我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼。
??:覺得有收穫請點個贊鼓勵一下!
?:收藏文章,方便回看哦!
?:評論交流,互相進步!