上一篇文章簡單瞭解了一下 ScopedModel 的用法,markdown
這一篇文章咱們來深刻 ScopedModel 源碼來看一下它的原理。併發
其實ScopedModel 只有一個文件,咱們直接打開 scoped_model.dart
文件,less
從上往下看。ide
首先在最上面的是 Model
,看一下源碼:ui
abstract class Model extends Listenable {
final Set<VoidCallback> _listeners = Set<VoidCallback>();
int _version = 0;
int _microtaskVersion = 0;
/// [listener] will be invoked when the model changes.
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
/// [listener] will no longer be invoked when the model changes.
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
/// Returns the number of listeners listening to this model.
int get listenerCount => _listeners.length;
/// Should be called only by [Model] when the model has changed.
@protected
void notifyListeners() {
// We schedule a microtask to debounce multiple changes that can occur
// all at once.
if (_microtaskVersion == _version) {
_microtaskVersion++;
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());
});
}
}
}
複製代碼
Model 繼承了 Listenable,至於爲何,看到後面就瞭解了。this
前兩個方法都好理解,增長/刪除一個監聽。spa
後面的 notifyListeners()
方法,設計
最主要的邏輯就是把_version++,並消除併發 change 時所可能引起的問題。code
邏輯就是首先判斷 _microtaskVersion 和 version 是否相等。cdn
若是相等,則把微任務(_microtaskVersion) ++,而後啓動一個微任務來把version++,並處理 listener()。
若是不相等就不作任務,這樣就消除了可能由於併發所引起的問題。
該類是用來查找 Model 的,可是已經棄用了,改用 ScopedModel.of
。
代碼以下:
/// Finds a [Model]. Deprecated: Use [ScopedModel.of] instead.
@deprecated
class ModelFinder<T extends Model> {
/// Returns the [Model] of type [T] of the closest ancestor [ScopedModel].
///
/// [Widget]s who call [of] with a [rebuildOnChange] of true will be rebuilt
/// whenever there's a change to the returned model.
T of(BuildContext context, {bool rebuildOnChange: false}) {
return ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange);
}
}
複製代碼
該類爲咱們所查看的重點,畢竟庫的名字就是它。
二話不說,先來看一下類上面的註釋:
/// Provides a [Model] to all descendants of this Widget.
///
/// Descendant Widgets can access the model by using the
/// [ScopedModelDescendant] Widget, which rebuilds each time the model changes,
/// or directly via the [ScopedModel.of] static method.
///
/// To provide a Model to all screens, place the [ScopedModel] Widget above the
/// [WidgetsApp] or [MaterialApp] in the Widget tree.
/// 向此小部件的全部後代提供[Model]。
///
/// 子代小部件能夠使用[ScopedModelDescendant]小部件訪問Model,該小部件在每次模型更改時重建,或者直接經過[ScopedModel.of]靜態方法進行訪問。
///
/// 要向全部頁面提供Model,請將[ScopedModel]小部件放在小部件樹中的[WidgetsApp]或[MaterialApp]上方。
複製代碼
註釋上面寫的很明顯了,若是你要向全部的頁面都提供Model,那麼就放在樹的最上方。
再看一下代碼:
class ScopedModel<T extends Model> extends StatelessWidget {
/// The [Model] to provide to [child] and its descendants.
final T model;
/// The [Widget] the [model] will be available to.
final Widget child;
ScopedModel({@required this.model, @required this.child})
: assert(model != null),
assert(child != null);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
/// Finds a [Model] provided by a [ScopedModel] Widget.
///
/// Generally, you'll use a [ScopedModelDescendant] to access a model in the
/// Widget tree and rebuild when the model changes. However, if you would to
/// access the model directly, you can use this function instead!
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();
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 須要一個泛型來確認 Model 的類型,而且是一個無狀態的 widget。
而後 ScopedModel 須要一個child Widget,這個 child Widget 就能夠說是咱們的 MaterialApp了。
接着是 build()
方法,這就是 ScopedModel 最精髓的地方。
build()方法使用了 AnimatedBuilder
來構建 widget,由於 AnimatedBuilder
會自動監聽 animation 數據的更改。
AnimatedBuilder
的參數 animation 須要的是一個 Listenable 對象,而咱們的 Model 正好繼承的就是Listenable。
因此咱們在自定義的 Model 中,須要更新的地方手動調用 notifyListeners()
。
notifyListeners()
前面也說了,就是把 _version++。
既然_version++了,那就達到了咱們想要的目的,更新使用了該數據的UI。
of() 方法咱們很熟悉,是用來獲取 Model 的。
用起來很簡單,可是裏面的內容卻很多,看一下源碼:
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();
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;
複製代碼
of() 方法須要傳入一個 Model 的泛型,
首先獲取了一下它的 Type,隨後根據 rebuildOnChange 的值用 inheritFromWidgetOfExactType
/ ancestorWidgetOfExactType
來查找widget。
這裏解釋一下這兩個方法:
inheritFromWidgetOfExactType
是用來獲取給定類型的最近的 widget,而且在值更新的時候自動從新構建。
ancestorWidgetOfExactType
是用來獲取給定類型最近的 祖先 Widget,而且在值更新的時候不從新構建。
因此這樣就控制住了沒有必要的UI更新。
在上面的build 和 of 方法中,都出現了該 Model,
他其實就是一個 InheritedWidget
,重寫了 updateShouldNotify
方法來控制重繪。
代碼以下:
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);
}
複製代碼
重繪的邏輯就是 version 是否相同。
該類就是一個包裝了 ScopedModel.of
方法的無狀態 widget。
代碼以下:
class ScopedModelDescendant<T extends Model> extends StatelessWidget {
/// Called whenever the [Model] changes.
final ScopedModelDescendantBuilder<T> builder;
/// An optional constant child that does not depend on the model. This will
/// be passed as the child of [builder].
final Widget child;
/// An optional constant that determines whether the
final bool rebuildOnChange;
/// Constructor.
ScopedModelDescendant({
@required this.builder,
this.child,
this.rebuildOnChange = true,
});
@override
Widget build(BuildContext context) {
return builder(
context,
child,
ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
);
}
}
複製代碼
具體就不細說了,由於幾乎沒有邏輯在裏面。
能夠看到ScopedModel 的設計真的是很是巧妙,
利用 AnimatedBuilder 和 InheritWidget 來作到全局的狀態管理。
關於微任務(Microtask) 和 EventQueue的理解,我會繼續出一篇文章來講一下。
那多個Model如何使用?利用 mixin 就能夠了。