Flutter中InheritedWidget的原理
Flutter的響應式開發與React類似,資料都是自頂向下的。
假設有祖先組點A,中間經過結點B, C,然後到結點D,D需要從A中獲取資料f,那按照自頂向下資料流轉,f需要依次傳遞給B及C,最後才到C。這樣開發極為不靈活,成本也比較高。所有Flutter需要有跨結點(只能是祖先後代節點,不能跨兄弟節點)高效傳遞資料的方案。
InheritedWidget
前面講過InheritedWidget,它是Flutter用來向後代結點高效傳遞資料用的。
當呼叫BuildContext.inheritFromWidgetOfExactType
時會建立兩個結點的依賴關係,每次當祖先組點資料改變後,會主動通知到後代結點,導致後代結點的重建(如果後代結點是StatefulWidget,還是呼叫State.didChangeDependencies
)。
那這種依賴關係是怎麼建立起來的?
每個Element維護了所有祖先遺傳結點(遺傳結點即InheritedWidget
對應的InheritedElement
,後面會沿用)的表_inheritedWidgets
,_inheritedWidgets
該表記錄了該結點至根結點所有的遺傳節點。
每個Element維護了自己依賴的遺傳結點的表_dependencies
。
_inheritedWidgets
記錄所有的祖先遺傳結點列表,而_dependencies
只記錄自己依賴的祖先遺傳結點列表。
InheritedElement
則維護了依賴自己的後代節點列表_dependents
。
當呼叫BuildContext.inheritFromWidgetOfExactType
方法時,如果找到相應型別的最近的遺傳節點,則將該遺傳結點新增到自己的依賴表中(_dependencies
),同時會呼叫祖先遺傳結點的InheritedElement.updateDependencies
方法,將自己註冊到InheritedElement
的_dependents
表中。
此時BuildContext.inheritFromWidgetOfExactType
已經完成依賴關係的構建,後面描述InheritedElement
的InheirtedWidget
發生變動後,InheritedElement
會遍歷_dependents
表並呼叫Element.didChangeDependencies
。
這節我們從原始碼入手,搞清楚這種依賴關係建立起來的原理。
Element怎麼維護所有祖先遺傳結點表_inheritedWidgets
abstract class Element extends DiagnosticableTree implements BuildContext {
Map<Type, InheritedElement> _inheritedWidgets;
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
...
_updateInheritance();
...
}
@mustCallSuper
void activate() {
...
_updateInheritance();
}
@mustCallSuper
void deactivate() {
...
_inheritedWidgets = null;
...
}
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
}
class InheritedElement extends ProxyElement {
@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;
}
}
複製程式碼
在Element樹第首次構建的過程中,都會呼叫Element.mount
,Element.mount
又會呼叫Element._updateInheritance
,如果該結點是InheritedElement
型別,則將自己新增到_inheritedWidgets
表中,否則,_inheritedWidgets
引用父結點的_inheritedWidgets
。隨著Element的遞迴構建,每個Element._inheritedWidgets
也被正確填充。
隨後,當該Element被從樹是移除時,會呼叫Element.deactivate
,會將_inheritedWidgets
置空。在動畫幀結束之前,如果該Element被重新整個進樹中(因為GlobalKey),會重新呼叫Element.activate
,進而重新填充_inheritedWidgets
。
Element怎麼維護自己依賴的所有祖先遺傳結點表_dependencies
abstract class Element extends DiagnosticableTree implements BuildContext {
Set<InheritedElement> _dependencies;
@mustCallSuper
void activate() {
...
_dependencies?.clear();
...
}
@mustCallSuper
void deactivate() {
...
if (_dependencies != null && _dependencies.isNotEmpty) {
for (InheritedElement dependency in _dependencies)
dependency._dependents.remove(this);
}
_inheritedWidgets = null;
...
}
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@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;
}
}
複製程式碼
當呼叫BuildContext.inheritFromWidgetOfExactType
時,如果在所有祖選遺傳表中找到相應遺傳結點,就將該遺傳結點新增到自己的依賴表中_dependencies
,同時將自己註冊到相應遺傳結點(呼叫InheritedElement.updateDependencies
註冊到InheritedElement.dependents
表中)。
當Element從樹中移除是,會呼叫Element.deactivate
,會遍歷所有自己註冊的依賴祖先遺傳結點,從遺傳結點中移除自己,這樣,與遺傳結點的依賴便取消了,而後將_inheritedWidgets
置空。
當Element重新整合進樹中時,會再次清理一下_inheritedWidgets
(以防從樹中移除後,仍然呼叫BuildContext.inheritFromWidgetOfExactType
)。
InheritedElement
的更新機制
class InheritedElement extends ProxyElement {
final Map<Element, Object> _dependents = HashMap<Element, Object>();
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);///super.updated會呼叫notifyClients
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@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);
}
}
}
複製程式碼
當InheritedElement需要更新時(相同位置再次配置了相同runtimeType及key的InheritedWidget)時,會再次呼叫InheritedWidget.updateShouldNotify
確認一下是否需要通知,如果確實需要通知,則會遍歷_denpendents
並呼叫Element.didChangeDependencies
。