Flutter中的InheritedWidget狀態管理
1.InheritedWidget是什麽?
InheritedWidget是Flutter中非常重要的壹個功能型組件,它提供了壹種數據在widget樹中從上到下傳遞、***享的方式,比如我們在應用的根widget中通過InheritedWidget***享了壹個數據,那麽我們便可以在任意子widget中來獲取該***享的數據!這個特性在壹些需要在widget樹中***享數據的場景中非常方便!如Flutter SDK中正是通過InheritedWidget來***享應用主題(Theme)和Locale (當前語言環境)信息的。
InheritedWidget和React中的context功能類似,和逐級傳遞數據相比,它們能實現組件跨級傳遞數據。InheritedWidget的在widget樹中數據傳遞方向是從上到下的,這和通知Notification的傳遞方向正好相反。
2.源碼分析
InheritedWidget
先來看下InheritedWidget的源碼:
abstract class?InheritedWidget?extends?ProxyWidget { ?const?InheritedWidget({ Key key,?Widget child }):?super(key: key,?child: child);?@override?InheritedElement?createElement() =>InheritedElement(this);?@protected?bool?updateShouldNotify(covariant?InheritedWidget oldWidget);}
它繼承自ProxyWidget:
abstract class?ProxyWidget?extends?Widget { ?const?ProxyWidget({ Key key,?@required?this.child?}) :?super(key: key);?final?Widget?child;}
可以看出Widget內除了實現了createElement方法外沒有其他操作了,它的實現關鍵壹定就是InheritedElement了。
InheritedElement 來看下InheritedElement源碼
class?InheritedElement?extends?ProxyElement { ?InheritedElement(InheritedWidget widget) :?super(widget);?@override?InheritedWidget?get?widget?=>?super.widget;?// 這個Set記錄了所有依賴的Elementfinal?Map<Element,?Object>?_dependents?=?HashMap<Element,?Object>();
//該方法會在Element mount和activate方法中調用,_inheritedWidgets為基類Element中的成員,用於提高Widget查找父節點中的InheritedWidget的效率,它使用HashMap緩存了該節點的父節點中所有相關的InheritedElement,因此查找的時間復雜度為o(1) ?@override?void?_updateInheritance() {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中調用,看名字就知道是通知依賴方該進行更新了,這裏首先會調用重寫的updateShouldNotify方法是否需要進行更新,然後遍歷_dependents列表並調用didChangeDependencies方法,該方法內會調用mardNeedsBuild,於是在下壹幀繪制流程中,對應的Widget就會進行rebuild,界面也就進行了更新 ?@override?void?notifyClients(InheritedWidget oldWidget) {assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for?(Element dependent?in?_dependents.keys) {?notifyDependent(oldWidget,?dependent);}?}
其中_updateInheritance方法在基類Element中的實現如下:
void _updateInheritance() {
?_inheritedWidgets = _parent?._inheritedWidgets;
}
總結來說就是Element在mount的過程中,如果不是InheritedElement,就簡單的將緩存指向父節點的緩存,如果是InheritedElement,就創建壹個緩存的副本,然後將自身添加到該副本中,這樣做會有兩個值得註意的點:
InheritedElement的父節點們是無法查找到自己的,即InheritedWidget的數據只能由父節點向子節點傳遞,反之不能。
如果某節點的父節點有不止壹個同壹類型的InheritedWidget,調用inheritFromWidgetOfExactType獲取到的是離自身最近的該類型的InheritedWidget。
看到這裏似乎還有壹個問題沒有解決,依賴它的Widget是在何時被添加到_dependents這個列表中的呢?
回憶壹下從InheritedWidget中取數據的過程,對於InheritedWidget有壹個通用的約定就是添加static的of方法,該方法中通過inheritFromWidgetOfExactType找到parent中對應類型的的InheritedWidget的實例並返回,與此同時,也將自己註冊到了依賴列表中,該方法的實現位於Element類,實現如下:
@overrideT?dependOnInheritedWidgetOfExactType
// 這裏通過上述mount過程中建立的HashMap緩存找到對應類型的InheritedElement final?InheritedElement ancestor =?_inheritedWidgets?==?nullnull?:?_inheritedWidgets[T];if?(ancestor !=?null) {assert(ancestor?is?InheritedElement);return?dependOnInheritedElement(ancestor,?aspect: aspect);?}?_hadUnsatisfiedDependencies?=?true;?return null;}
@overrideInheritedWidget?dependOnInheritedElement(InheritedElement ancestor,?{ Object aspect }) {?assert(ancestor !=?null);
// 這個列表記錄了當前Element依賴的所有InheritedElement,用於在當前Element deactivate時,將自己從InheritedElement的_dependents列表中移除,避免不必要的更新操作 ?_dependencies=?HashSet<InheritedElement>();?_dependencies.add(ancestor);?ancestor.updateDependencies(this,?aspect);return?ancestor.widget;}
3.如何使用InheritedWidget
1)、創建壹個類繼承自Inheritedwidget
class?InheritedContext?extends?InheritedWidget{?final?InheritedTestModel?inheritedTestModel;?InheritedContext({Key key,@required?this.inheritedTestModel,@required?Widget child}):?super(key: key,?child: child);static?InheritedContext? of (BuildContext context) {return?context.dependOnInheritedWidgetOfExactType<InheritedContext>();?}?@override?bool?updateShouldNotify(InheritedContext oldWidget) {return?inheritedTestModel?!= oldWidget.inheritedTestModel;?}}
2)、InheritedTestModel類為數據容器(這裏定義了壹個List<int>數據源)
class?InheritedTestModel{?final?List?_list;?InheritedTestModel(this._list);?List?getList(){return?_list;?}}
class?ArrayListData{?static?List? _list ;static?List? getListData (){ _list? =?new?List(); _list .add(1); _list .add(2); _list .add(3); _list .add(4);return? _list ;?}}
3)、定義壹個Widget?使用?InheritedContext類的數據?InheritedTestModel?
class?ListDemo?extends?StatefulWidget{?@override?State?createState() {return new?ListDemoState();?}}class?ListDemoState?extends?State<ListDemo>{List?_list;?InheritedTestModel?_inheritedTestModel;?Timer?_timer;?Duration?oneSec?=?const?Duration(seconds:?1);?@override?void?initState() {_list?= ArrayListData. getListData ();_inheritedTestModel?=?new?InheritedTestModel(_list);_timer?=?Timer.periodic(oneSec,?(timer) {?_doTimer();});?}?void?_doTimer() {for(int i =?0;?i <?_list.length;?i++){?_list[i] =?_list[i]+?1;}?setState(() {_inheritedTestModel?=?new?InheritedTestModel(_list);?});?}Widget?_buildBody() {return?Container(child:?ListDemo2(),);?}?@override?Widget?build(BuildContext context) {return?InheritedContext(inheritedTestModel:?_inheritedTestModel,?child:?Scaffold(appBar:?AppBar(title:?Text("ListDemo"),actions: <Widget>[IconButton(icon:?Icon(Icons. add ),)],),body: _buildBody(),?),);?}?@override?void?dispose() {super.dispose();if?(_timer?!=?null) {?_timer.cancel();}?}}
4)、在ListDemo中通過Timer更新InheritedTestModel?中的數據,然後再下壹個Widget中獲取更新的數據作為展示
class?ListDemo2?extends?StatefulWidget{?@override?State?createState() {return new?ListDemoState2();?}}class?ListDemoState2?extends?State<ListDemo2>{InheritedTestModel?_inheritedTestModel;?Widget?_buildListItem(BuildContext context,int index) {return ?Container(height:?50,width:?100,alignment: Alignment. center ,child:?Text(_inheritedTestModel.getList()[index].toString()),);?}Widget?_buildBody() {_inheritedTestModel?= InheritedContext. of (context).inheritedTestModel;return?Container(child:?ListView.builder(itemBuilder:(context,?index)=>_buildListItem(context,index),itemCount:?_inheritedTestModel.getList().length,),);?}?@override?Widget?build(BuildContext context) {return ?_buildBody();?}}
這樣就可以在父widget中更新數據,子View不需任何操作直接從數據容器InheritedTestModel?中獲取到更新後的新數據
這是壹個數據***享的簡單的例子,基本的流程,大致就是A去更新B的數據,A和B有壹個***同的父類,實現數據的***享
4.上面說了原理和基本的使用,但是在實際項目當中,我當然不建議這樣來使用,Google?已經為我們封裝好了功能更加強大的插件Provider,其內部原理就是基於InheritedWidget來實現的,我們理解了基本原理,可以更好的在項目中運用Provider