背景
Flutter提供了用于Widget間共享資料的InheritedWidget,當InheritedWidget發生變化時,它的子樹中所有依賴了它的資料的Widget都會進行rebuild,這使得開發者省去了維護資料同步邏輯的麻煩,InheritedWidget看名字就是一個Widget,那么它是如何做到資料共享,如何做到通知更新的呢?下面我們一起來看看,
InheritedWidget用法
InheritedWidget用法分為以下幾個步驟;
- 自定義Widget繼承自InheritedWidget,并且自定義static of方法,用于child獲取當前實體
- 實作
InheritedWidtet類中的updateShouldNotify方法,用于回傳update條件; - 當資料變化時,呼叫自定義widget的State類中的setState()方法,觸發整棵InheritedWIdget tree的更新,
代碼就不貼了,相當簡單,下面我們來一步步分析它的原理,
原理
InheritedWidget原始碼
先來看看InheritedWidget的原始碼部分
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
他繼承自ProxyWidget,我們再來看看proxyWidget
abstract class ProxyWidget extends Widget {
const ProxyWidget({ Key? key, required this.child }) : super(key: key);
final Widget child;
}
通過上面的總共不超過10行代碼的原始碼,我們看出來InheritedWidget內部除了實作createElement()創建InheritedElement之外就沒有其他的操作,我們繼續看InheritedElement
InheritedElement原始碼
class InheritedElement extends ProxyElement {
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get 這個Set記錄了所有依賴的Element => super.widget as InheritedWidget;
//這個Set記錄了所有依賴的Element,這里的value值一般都是null
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
// 該方法會在Element mount和activate方法中呼叫
// _inheritedWidgets為基類Element中的成員,快取父節點中所有相關的InheritedElement
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets![widget.runtimeType] = this;
}
//該方法在父類ProxyElement的update(ProxyWidget oldWidget)方法中呼叫,看名字就知道是通知依賴方該進行更新了,這里首先會呼叫重寫的updateShouldNotify方法是否需要進行更新,然后遍歷_dependents串列并呼叫didChangeDependencies方法,該方法內會呼叫mardNeedsBuild,于是在下一幀繪制流程中,對應的Widget就會進行rebuild,界面也就進行了更新
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
// 更新所有的子項
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
}
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
我們看到InheritedElement同樣繼承自ProxyElement,核心方法也就三個,根據注釋理解
下面我們繼續看,既然Element在update的時候,會呼叫到notifyClients方法,從而遍歷更新所有的_dependens,那么_InheritedElement的dependents是在什么時候被添加進去的呢?
代碼最后跟進到:Element.dependOnInheritedElement方法
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
//在ancestor.updateDependencies方法中更新_dependents[dependent],以this作為key
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
// 這里呼叫了dependOnInheritedElement方法
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
相信大家如果看過深入理解BuildContext一文或者實際使用過InheritedWidget的朋友對這兩個方法肯定不陌生,對于想讀取上級Widget共享的資料來說,通俗的約定就是在上級的Widget類,定義static的of方法,該方法中通過dependOnInheritedWidgetOfExactType獲取parent中最近的InheritedWidget的實體并回傳,并且同時將自己注冊到_dependents中,這樣當parent的InheritedWidget更新的時候,就會間接通過notifyCLients方法來更新自己了,
總結
- InheritedElement的父節點們是無法查找到自己的,即InheritedWidget的資料只能由父節點向子節點傳遞,反之不能,
- 如果某節點的父節點有不止一個同一型別的InheritedWidget,呼叫
dependOnInheritedWidgetOfExactType獲取到的是離自身最近的該型別的InheritedWidget,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/356136.html
標籤:其他
