這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰數組
上一篇咱們從源碼角度分析了 setState 的過程,從而瞭解到爲何 setState 方法被調用的時候會從新構建整個 Widget 樹。可是,Widget 樹的從新構建並不意味着渲染元素樹也須要從新構建,事實上渲染樹只是作了更新,而不必定是移除後在渲染。微信
可是,咱們的 ModelBinding類也是使用了 setState 進行狀態更新的,爲何它的子組件沒有從新構建,而只是更新了依賴於狀態的子組件的 build 方法呢?除了使用了內部的 InheritedWidget包裹了子組件外,其餘和普通的 StatefulWidget 沒什麼區別。如前面兩篇分析 從InheritedWidget瞭解狀態管理同樣,差異就是在這個 InheritedWidget上。本着技術人刨根問底的精神,本篇就來看一下 InheritedWidget 在調用 setState的時候究竟有什麼不一樣。markdown
知其然,知其因此然。在閱讀本篇文章前,若是對 Flutter 的狀態管理不是特別清楚的,建議閱讀前幾篇文章瞭解一下背景:app
首先,InheritedWidget
和 StatefulWidget
的繼承鏈不一樣,對好比下。
InheritedWidget
繼承自 ProxyWidget
,以後纔是 Widget
,而 StatefulWidget
直接繼承 Widget
。 其二是建立的渲染元素類不一樣,InheritedWidget
的 createElement
返回的是InheritedElement
,而 StatefulWidget
的 createElement
返回的是StatefulElement
。less
咱們在上一篇已經知道,實際的渲染控制是有 Element
類來完成的,實際上Widget
的createElement
方法就是將 Widget
對象傳給 Element
對象,由 Element
對象根據 Widget
的組件配置來決定如何渲染。ide
InhretiedWidget
的定義很簡單,以下所示:源碼分析
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);
}
複製代碼
updateShouldNotify
方法用於 InheritedWidget
的子類實現,已決定是否通知其子組件(widget
)。例如,若是數據沒有發生改變(典型的以下拉刷新沒有新的數據),那麼就能夠返回 false
,從而無需更新子組件,減小性能消耗。以前咱們的 ModelBinding
例子中是直接返回了 true
,也就是每次發生變化都會通知子組件。接下來就看 InheritedElement
和 StatefulElement
的區別了。post
上一篇咱們已經分析過 StatefulElement
了,他在 setState
後會調用重建方法 performRebuild
。performRebuild
方法在父類Component
中實現的。核心是當 Widget
樹發生改變後,根據新的 Widget
樹調用 updateChild
方法來更新子元素。性能
而上一篇的 ModelBinding
調用 setState
的時候,由於它自身是一個 StatefulWidget
,毫無疑問它也會調用到 updateChild
來更新子元素。從執行結果來看,因爲 ModelBinding
的例子中沒有出現從新構建 Widget
樹的狀況,所以應該是在 updateChild
前的處理不一樣。 在 updateChild
以前會調用組件的 build
方法來獲取新的 Widget
樹。是這裏不一樣嗎?繼續往下看。字體
與 InheritedWidget
對應,InheritedElement
上面還多了一層繼承,那就是 ProxyElement
。而偏偏在 ProxyElement
咱們找到了build
方法。與 StatefulElement
不一樣,這裏的 build
方法沒有調用對應 Widget
對象的 build
方法,而是直接返回了 widget.child
。
// ProxyElement的 build 方法
@override
Widget build() => widget.child;
// StatefulElement 的 build 方法
@override
Widget build() => state.build(this);
// StatelessElement 的 build方法
@override
Widget build() => widget.build(this);
複製代碼
由此咱們就知道了爲何 InheritedWidget
在狀態更新的時候爲何沒有從新構建其子組件樹了,這是由於在ProxyElement
中直接就返回了已經構建的子組件樹,而不是重建。你是否是覺得真相大白了?說好的刨根問底呢?難道咱們不該該問問若是子組件樹發生了改變,ProxyElement
是如何感知的?好比插入了一個新的元素,或者某個元素的渲染參數變了(顏色,字體,內容等),渲染層是怎麼知道的?繼續繼續!
先看一下 InheritedElement 的類結構。
classDiagram Element <-- ComponentElement ComponentElement <-- ProxyElement ProxyElement <-- InheritedElement class Element { -dependOnInheritedWidgetOfExactType() -dependOnInheritedElement() } class InheritedElement { -Map<Element, Object?> _dependents -void _updateInheritance() -getDependencies(Element dependent) setDependencies(Element dependent, Object? value) updateDependencies(Element dependent, Object? aspect) notifyDependent(covariant InheritedWidget oldWidget, Element dependent) updated(InheritedWidget oldWidget) notifyClients(InheritedWidget oldWidget) } class ProxyElement { -build() -update(ProxyWidget newWidget) -updated(covariant ProxyWidget oldWidget) -notifyClients(covariant ProxyWidget oldWidget) }
從類結構上看也不復雜,這是由於大部分渲染的管理已經在父類的 ComponentElement
和 Element
中完成了。build
方法咱們已經講過了,重點來看一下在 InheritedWidget
的父組件調用 setState
後的過程。 咱們在子組件須要獲取狀態管理的時候,使用的方法是:
ModelBindingV2.of<FaceEmotion>(context)
複製代碼
這個方法實際調用的是:
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
複製代碼
這裏的dependOnInheritedWidgetOfExactType
方法在 BuildContext
定義,但其實是Element
實現。這裏會訪問一個HashMap
對象_inheritedWidgets
,從數組中找到對應類型的InheritedElement
。
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
{Object? aspect}) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
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);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
複製代碼
這個數組其實是在 mount
方法中調用_updateInheritance
中完成初始化的。而在InheritedElement
中重載了 Element
的這個方法。也就是在建立 InheritedWidget
的時候,在 mount
中就將 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;
}
複製代碼
首先這個方法會將父級的所有 InheritedWidgets延續下來,而後在將本身(InheritedElement)存入到這個 HashMap中,以便後續可以找到該元素。
所以,當在子組件中使用dependOnInheritedWidgetOfExactType
的時候,實際上執行的是 dependOnInheritedElement
方法,傳遞的參數是經過類型找到的 InheritedElement
元素和指定的 InheritedWidget
類型參數 aspect
,這裏就是咱們的_ModeBindScope<T>
,而後會將當前的渲染元素(Element 子類)與其綁定,告知 InheritedElement
對象這個組件會依賴於它的InheritedWidget
。咱們從調試的結果能夠看到,在_dependents
中存在了這麼一個對象。就這樣,InheritedElement
就和組件對應的渲染元素創建了聯繫。
接下來就是看 setState
後,怎麼獲取新的組件樹和更新組件了。咱們已經知道了setState
的時候會調用 performRebuild
方法,在 performRebuild
中會調用 Element
的 updateChild
方法,如今來看InheritedElement
的updateChild
作了什麼事情。實際上 updateChild
會調用 child.update(newWidget)
方法:
else if (hasSameSuperclass &&
Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
child.update(newWidget);
//...
newChild = child;
}
// ...
return newChild;
複製代碼
而在 ProxyElement
中,重寫了 update
方法。
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
複製代碼
這裏的 newWidget 是 setState 的時候構建的新的組件配置,所以和 oldWidget 並不相同。對於 InheritedWidget,它會先調用updated(oldWidget),這個方法實際上就是通知依賴 InheirtedWidget 的組件更新:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
// InheritedElement類
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget);
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final 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);
}
}
}
複製代碼
實際上最終調用了依賴 InheritedWidget 組件渲染元素的 didChangeDependencies 方法,咱們在這個方法打印出來看一下。 在元素的
didChangeDependencies
中就會調用 markNeedsBuild
將元素標記爲須要更新,而後後續的過程就和 StatefulElement
的同樣了。而對於沒有依賴狀態的元素,由於沒有在_dependent
中,所以不會被更新。 而 ModelBinding
所在的組件是 StatelessWidget
,所以最初的這個 Widget
配置樹一旦建立就不會改變,而子組件樹若是要 改變的話只有兩種狀況: 一、子組件是 StatefulWidget
,經過setState
改變,那這不屬於 InheritedWidget 的範疇了,而是經過 StatefulWidget 的更新方式完成——固然,這種作法不推薦。 二、子組件的組件樹改變依賴於狀態嗎,那這個時候天然會在狀態改變的時候更新。
由此,咱們終於弄明白了InheritedWidget的組件樹的感知和通知子組件刷新過程。
從 InheritedWidget 實現組件渲染的過程來看,整個過程分爲下面幾個步驟:
狀態管理的原理性文章講了好幾篇了,經過這些文章但願可以達到知其然,知其因此然的目的。實際上,Flutter 的組件渲染的核心就在於如何選擇狀態管理來實現組件的渲染,這個對性能影響很大。接下來咱們將以狀態管理插件的應用方式,講述在實際例子中的應用。
我是島上碼農,微信公衆號同名,這是Flutter 入門與實戰的專欄文章。
👍🏻:以爲有收穫請點個贊鼓勵一下!
🌟:收藏文章,方便回看哦!
💬:評論交流,互相進步!