當前位置:成語大全網 - 新華字典 - flutter 狀態管理 InheritedWidget 原理分析

flutter 狀態管理 InheritedWidget 原理分析

最近公司做技術分享寫的文章的demo

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