在Flutter中,Widget可以說是第一基礎概念。Widget是對用戶界面的不可變描述,可被膨化為管理底層渲染樹的Element。
理解Widget原理是掌握Flutter編程至關重要的一步,本系列主要介紹Widget的基礎知識,本文是第三篇:
- StatelessWidget
- StatefulWidget
- InheritedWidget
- Key
簡介
前面講過了StatelessWidget和StatefulWidget,當我們的App變得越來越大、Widget樹越來越復雜時,傳遞和訪問數(shù)據(jù)會變得非常繁瑣。

假設每個Widget嵌套了四、五個Widget,我們希望在最底層的Widget訪問頂層Widget的數(shù)據(jù),如果只使用StatelessWidget和StatefulWidget,我們不得不將數(shù)據(jù)添加在每個Widget的構造函數(shù)里,如果需要添加、修改、刪除數(shù)據(jù),那簡直就是噩夢,一串構造函數(shù)都需要修改。你應該已經猜到了,InheritedWidget就是解決這個問題的。

當我們將InheritedWidget添加到Widget樹中時,在其下面的所有Widget都可以得到一個指向它的引用,因此我們稱其為Inherited Widget。
假設有一個InheritedWidget的子類:InheritedAppState:
class InheritedAppState extends InheritedWidget {
final AppState appState;
InheritedAppState({this.appState, Widget child}): super(child: child);
@override
bool updateShouldNotify(_InheritedStateContainer old) => true;
static InheritedAppState of(BuildContext context) =>
context.inheritFromWidgetOfExactType(InheritedAppState);
}
在子孫Widget中,可以直接通過context.inheritFromWidgetOfExactType(InheritedAppState)獲取InheritedAppState對象。為了提升代碼簡潔性和易讀性,InheritedWidget一般會包含一個靜態(tài)的of方法。
我們注意到,InheritedWidget是不可變的,因此上面的appState屬性是final類型的。當InheritedWidget的數(shù)據(jù)改變時,我們只能重建整個widget。請謹記這一點,所有的Widget都是不可變的,不要改變widget中保存的數(shù)據(jù)。
final僅表示屬性不能被再次賦值,不代表內部不能改變。比如,我們可以將附加的數(shù)據(jù)模型改為一個service對象,這個service可以訪問數(shù)據(jù)庫、網(wǎng)絡請求、本地assets資源等。
class InheritedAppState extends InheritedWidget {
final AppStateService service;
InheritedAppState({this.service, Widget child}): super(child: child);
@override
bool updateShouldNotify(_InheritedStateContainer old) => true;
static InheritedAppState of(BuildContext context) =>
context.inheritFromWidgetOfExactType(InheritedAppState);
}
簡單來說,InheritedWidget提供了一種在widget樹中從上到下傳遞、共享數(shù)據(jù)的方式。Flutter SDK正是通過InheritedWidget來共享應用主題和Locale等信息的。
原理
對于InheritedWidget來說,需要實現(xiàn):
- 在子孫widget中可以方便地獲取到
InheritedWidget或者保存在InheritedWidget中的數(shù)據(jù) - 當
InheritedWidget中的數(shù)據(jù)改變時,更新所有依賴的子孫widget
下面我們分別介紹實現(xiàn)原理。
獲取InheritedWidget對象
我們已經知道,通過context.inheritFromWidgetOfExactType可以在子孫widget中獲取上層的InheritedWidget對象,具體是如何實現(xiàn)的呢?
BuildContext實際上就是Element,其相關實現(xiàn)代碼如下:
Map<Type, InheritedElement> _inheritedWidgets;
@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;
}
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
可以看到,在Element中,有一個Map類型的屬性_inheritedWidgets,鍵是Type,而值是InheritedElement。當我們調用inheritFromWidgetOfExactType方法時,直接從這個Map中查找對應類型的InheritedElement,并調用其updateDependencies方法注冊依賴,然后返回其關聯(lián)的widget對象。
那么_inheritedWidgets的值從哪兒來呢?從parent那兒來。那parent的值又從哪兒來呢?從距離最近的InheritedElement祖先那兒來。InheritedElement中的_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;
}
很簡單,獲取parent中的值,并注冊自身。
因此,一個普通Element中的_inheritedWidgets,保存的就是其所有InheritedElement祖先節(jié)點的信息,通過這個屬性,我們就可以方便地、高效率地獲取到樹上層的InheritedWidget了。
更新子孫widget
這個相對簡單,不涉及新的知識,當上層的InheritedWidget變化時,其widget子樹會重建,子孫widget在其build方法中就可以獲取到新的值了。
為了確保子孫widget中的數(shù)據(jù)能及時更新,只應該在build方法、layout和paint回調以及State.didChangeDependencies方法中調用inheritFromWidgetOfExactType方法。
不應該在widget的構造方法或者State.initState方法中調用inheritFromWidgetOfExactType方法,因為當數(shù)據(jù)變更時,這些方法不會被再次調用。也不應該在State.dispose方法中調用,因為此時element樹是不穩(wěn)定的。如果必須要在State.dispose中訪問上層的InheritedWidget,可以提前在State.didChangeDependencies方法中保存一個引用。在State.deactivate方法中調用是安全的,此方法在widget從樹中移除時被調用 。
如果我們希望在InheritedWidget更新時做一些處理怎么辦?比如從網(wǎng)絡獲取數(shù)據(jù),如果放在build中代價顯然太高了,從設計模式的角度來說也極其不合理,build方法最好只負責構建widget樹。
State有一個didChangeDependencies方法,當其依賴的對象發(fā)生改變時會被調用,最主要的場景就是InheritedWidget依賴。此方法在initState方法結束之后也會被馬上調用一次,可以在這個方法中安全地調用BuildContext.inheritFromWidgetOfExactType方法。子類很少需要重寫此方法,因為框架總會在依賴改變時調用build方法。只有當需要執(zhí)行網(wǎng)絡請求這樣的昂貴操作時,才需要重寫此方法。