前面我們講了InheritedWidget的用途和用法,還留了一個小問題。前文連結
Flutter framework是怎麼知道子widget有沒有依賴InheritedWidget的?
InheritedWidget 定義
首先看一下 InheritedWidget 的定義:
abstract class InheritedWidget extends ProxyWidget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
/// Whether the framework should notify widgets that inherit from this widget.
///
/// When this widget is rebuilt, sometimes we need to rebuild the widgets that
/// inherit from this widget but sometimes we do not. For example, if the data
/// held by this widget is the same as the data held by `oldWidget`, then we
/// do not need to rebuild the widgets that inherited the data held by
/// `oldWidget`.
///
/// The framework distinguishes these cases by calling this function with the
/// widget that previously occupied this location in the tree as an argument.
/// The given widget is guaranteed to have the same [runtimeType] as this
/// object.
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
複製程式碼
繼承自 ProxyWidget, 包含createElement()
和 updateShouldNotify
兩個方法。updateShouldNotify
的註釋較為完整,
定義了是否需要通知子樹,那麼該如何通知呢?我們看一下createElement()
返回的InheritedElement
是什麼:
class InheritedElement extends ProxyElement {
...
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@override
void _updateInheritance() {
assert(_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;
}
....
}
複製程式碼
其中 _parent?._inheritedWidgets 又是什麼? 繼續跟蹤發現, 這是Element
擁有的屬性:
Map<Type, InheritedElement> _inheritedWidgets;
複製程式碼
其中儲存了祖先節點中出現的 InheritedWidget 與其對應 element 的對映關係。在 element 的 mount 階段
和 active 階段
,會執行 _updateInheritance() 方法更新這個對映關係。
對於普通 Element 例項,_updateInheritance() 只是單純把父 element 的 _inheritedWidgets 屬性儲存在自身 _inheritedWidgets 裡。從而實現對映關係的層層向下傳遞。
@override
void _updateInheritance() {
assert(_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;
}
複製程式碼
此時我們再回過頭來看,InheritedElement的 _updateInheritance
的不同,InheritedElement 例項會把自身的資訊新增到 _inheritedWidgets 屬性中,這樣其子孫 element 就可以通過前面提到的 _inheritedWidgets 的傳遞機制獲取到此 InheritedElement 的引用。
鋪墊的差不多,可以看InheritedWidget如何進行更新通知了。
InheritedWidget 更新通知機制
前文提到,想要獲取"最近"的InheritedElement,需要呼叫 BuildContext.inheritFromWidgetOfExactType
。那跟Element
有什麼關係呢?
其實,Element
就是BuildContext
。原始碼定義如下:
abstract class Element extends DiagnosticableTree implements BuildContext
複製程式碼
接下來看BuildContext.inheritFromWidgetOfExactType
的原始碼:
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
複製程式碼
首先在 _inheritedWidget 對映中查詢是否有特定型別 InheritedWidget 的例項。如果有則將該例項新增到自身的依賴列表中,同時將自身新增到對應的依賴項列表中。這樣該 InheritedWidget 在更新後就可以通過其 _dependents 屬性知道需要通知哪些依賴了它的 widget。
每當 InheritedElement 例項更新時,會呼叫 InheritedWidget
中的updated
方法:
/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
///
/// Called by [update], immediately prior to [build].
///
/// Calls [notifyClients] to actually trigger the notifications.
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
複製程式碼
上面提到繼承自 ProxyWidget, 我們來看 ProxyWidget的 updated 方法:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
複製程式碼
又回到了 InheritedWidget 中notifyClients :
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies.contains(this));
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
複製程式碼
終於看到熟悉的didChangeDependencies
了,哈哈,終於可以解釋 前文中 如果_TestWidget的build方法中沒有使用ShareDataWidget的資料,那麼它的didChangeDependencies()將不會被呼叫,因為它並沒有依賴ShareDataWidget。
總結一下,首先執行相應 InheritedWidget 上的 updateShouldNotify 方法判斷是否需要通知,如果該方法返回 true 則遍歷 _dependents 列表中的 element 並執行他們的 didChangeDependencies() 方法。這樣 InheritedWidget 中的更新就通知到依賴它的子 widget 中了。
原始碼分析結束了,後續可能會再講一些 InheritedWidget
的具體應用。
如果你覺得這篇文章對你有益,還請幫忙轉發和點贊,萬分感謝。