參考
scoped_model 是 Google 推薦使用的應用狀態管理庫(Simple app state management),當然,除了它,還有其他選擇,比如 Redux,Rx,hooks 等等。
臨時狀態和應用狀態
我們說 scoped_model 用於應用狀態管理,是因為除了**應用狀態(app state)**以外,還有臨時狀態(ephemeral state)。
臨時狀態
也可以稱為 UI 狀態或者本地狀態,是一種可以包含在單個 widget 的狀態。
比如以下定義:
PageView
當前的頁面- 動畫當前的進度
BottomNavigationBar
當前選中項
使用臨時狀態,我們不需要使用狀態管理,只需要一個 StatefulWidget
。
應用狀態
需要在應用中多個元件之間共享,而且在不同的使用者會話之間保持,這就是應用狀態,有時候也稱為共享狀態。
比如以下定義:
- 使用者偏好設定
- 登入資訊
- 通知欄功能
- 購物車功能
- 已讀未讀功能
臨時狀態和應用狀態之間沒有非常明確的界限,比如你可能需要持久化儲存 BottomNavigationBar
的選中項,那它就不是臨時狀態了,而是應用狀態。如果你願意,你甚至可以不用任何狀態管理庫,只使用 State
和 setState()
來管理應用所有的狀態。
scoped_model
scoped_model 由三個部分組成,ScopedModel
、ScopedModelDescendant
和 Model
。
Model
Model
是定義一個資料模型的基類,它繼承了 Listenable
,提供了 notifyListeners()
方法來通知元件需要更新。
@protected
void notifyListeners() {
// We schedule a microtask to debounce multiple changes that can occur
// all at once.
if (_microtaskVersion == _version) {
// 去抖
_microtaskVersion++;
// 安排一個 Microtask 任務
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
// Convert the Set to a List before executing each listener. This
// prevents errors that can arise if a listener removes itself during
// invocation!
_listeners.toList().forEach((VoidCallback listener) => listener());
});
}
}
複製程式碼
關於 Microtask:這涉及到 dart 的單執行緒模型,這裡我們只需要知道,它的優先順序比 event 要高,會先執行。
關於更多資訊,可以檢視 event-loop
ScopedModel
在定義一個 Model
後,我們需要再定義一個 ScopedModel
作為 root widget,它有兩個引數,一個是 model
即我們上面定義的 Model
例項,另外一個是 child
,則是我們定義的 widget。ScopedModel
是一個 StetelessWidget
,我們看下它的 build()
方法:
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
複製程式碼
AnimatedBuilder
是一個用於構建動畫的 widget,它的 animation
引數是一個 Listenable
類:
abstract class Listenable {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Listenable();
/// Return a [Listenable] that triggers when any of the given [Listenable]s
/// themselves trigger.
///
/// The list must not be changed after this method has been called. Doing so
/// will lead to memory leaks or exceptions.
///
/// The list may contain nulls; they are ignored.
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;
/// Register a closure to be called when the object notifies its listeners.
void addListener(VoidCallback listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies.
void removeListener(VoidCallback listener);
}
複製程式碼
AnimatedBuilder
會呼叫 addListener()
方法新增一個監聽者,然後呼叫 setState()
方法進行 rebuild。從上面可知,Model
繼承 Listenable
類。這也是為什麼在修改值後需要呼叫 notifyListeners()
的原因。
再看下 builder
引數,它實際上返回了一個 _InheritedModel
例項:
class _InheritedModel<T extends Model> extends InheritedWidget {
final T model;
final int version;
_InheritedModel({Key key, Widget child, T model})
: this.model = model,
this.version = model._version,
super(key: key, child: child);
@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
(oldWidget.version != version);
}
複製程式碼
InheritedWidget
是 scoped_model
的核心。
InheritedWidget
InheritedWidget
可以在元件樹中有效的傳遞和共享資料。將 InheritedWidget
作為 root widget,child widget 可以通過 inheritFromWidgetOfExactType()
方法返回距離它最近的 InheritedWidget
例項,同時也將它註冊到 InheritedWidget
中,當 InheritedWidget
的資料發生變化時,child widget 也會隨之 rebuild。
當 InheritedWidget
rebuild 時,會呼叫 updateShouldNotify()
方法來決定是否重建 child widget。
繼續看 ScopedModel
,它使用 version
來判斷是否需要通知 child widget 更新:
@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
(oldWidget.version != version);
複製程式碼
當我們呼叫 Model
的 notifyListeners()
方法時,version
就會自增。
ScopedModelDescendant
ScopedModelDescendant
是一個工具類,用於獲取指定型別的 Model
,當 Model
更新時,會重新執行 build()
方法:
@override
Widget build(BuildContext context) {
return builder(
context,
child,
ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
);
}
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();
// 最終也是使用 inheritFromWidgetOfExactType 或 ancestorWidgetOfExactType
Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorWidgetOfExactType(type);
if (widget == null) {
throw new ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}
static Type _type<T>() => T;
複製程式碼
注意到,在呼叫 ScopedModel.of()
方法時,有個 rebuildOnChange
引數,表示當 Model
更新時,是否需要 rebuild。當設定為 false
時,會使用 ancestorWidgetOfExactType()
方法去獲取最近的 InheritedWidget
,和 inheritFromWidgetOfExactType()
方法的區別是,inheritFromWidgetOfExactType
在獲取的同時會註冊到 InheritedWidget
上。
總結
使用 scoped_model
,我們首先定義一個 Model
,這裡面封裝了對資料的操作,需要注意,資料改變後需要呼叫 notifyListeners()
方法。接著再將 ScopedModel
作為 root widget,傳遞一個 Model
例項,最後我們可以使用 ScopedModelDescendant
來響應資料的修改,也可以手動呼叫 ScopedModel.of()
方法來獲取 Model
例項,呼叫這個方法,如果引數 rebuildOnChange
傳遞為 true
,則同時會將當前 widget 註冊到 InheritedWidget
上,當資料改變時,當前 widget 會重新構建。