Flutter 入門與實戰(六十六):聽說 MobX 很流行,進來了解一下?

島上碼農發表於2021-08-29

這是我參與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 的核心要素有三個:、ObservablesActions、和 Reactions,如下圖所示。接下來以簡單的 Counter 計數器應用來介紹各個元素的使用。

MobX 核心要素

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_runnermobx_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實體類,其中 firstNamelastName 組成了 Contact 的核心狀態。然而,fullName 是一個派生狀態,通過 firstNamelastName 組合得到。對於這種情況,依賴於核心狀態或其他派生狀態的稱之為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三元素的observablesactionsreactions的完結部分。Reactions是響應式系統中的觀察者,一旦跟蹤的 observable物件發生改變後就會被通知到。Reactions 有幾種不同的方式。所有方式都會返回一個 ReactionDisposer 方法,呼叫該方法可以銷燬該 reactionReactions 的一個典型特性是自動跟蹤所有的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 入門與實戰專欄原始碼

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章