在Tree中從上往下高效傳遞數(shù)據(jù)的基類widget , 定義為:abstract class InheritedWidget extends ProxyWidget
成都創(chuàng)新互聯(lián)公司成都網(wǎng)站建設(shè)定制網(wǎng)站設(shè)計(jì),是成都網(wǎng)站制作公司,為成都玻璃鋼坐凳提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計(jì)服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計(jì)、前端HTML5制作、后臺(tái)程序開(kāi)發(fā)等。成都網(wǎng)站設(shè)計(jì)熱線:18982081108
Flutter的響應(yīng)式開(kāi)發(fā)與React類似,數(shù)據(jù)都是自頂向下的。
假設(shè)有祖先組點(diǎn)A,中間經(jīng)過(guò)結(jié)點(diǎn)B, C,然后到結(jié)點(diǎn)D,D需要從A中獲取數(shù)據(jù)f,那按照自頂向下數(shù)據(jù)流轉(zhuǎn),f需要依次傳遞給B及C,最后才到C。這樣開(kāi)發(fā)極為不靈活,成本也比較高。所有Flutter需要有跨結(jié)點(diǎn)(只能是祖先后代節(jié)點(diǎn),不能跨兄弟節(jié)點(diǎn))高效傳遞數(shù)據(jù)的方案。
大體意思如下:
InheritedWidget 是在樹(shù)中高效向下傳遞信息的基類部件;
調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例;
在 InheritedWidget 類型的控件被引用,也就是調(diào)用過(guò) inheritFromWidgetOfExactType 方法后,當(dāng) InheritedWidget 自身狀態(tài)改變時(shí),會(huì)導(dǎo)致引用了 InheritedWidget 類型的子控件重構(gòu)(rebuild)。
這里隨便定義一個(gè)人 Person 類。
創(chuàng)建一個(gè)類繼承 InheritedWidget,并實(shí)現(xiàn) updateShouldNotify 方法。
之前說(shuō)到調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例,所以此處定義一個(gè)靜態(tài)的 of 方法,通過(guò)傳入的 context 獲取到最近的 InheriedDataWidget 實(shí)例。
1.定義數(shù)據(jù)模型
這里隨便定義一個(gè) Person 類。
2.自定義 InheritedWidget 控件類
創(chuàng)建一個(gè)類繼承 InheritedWidget,并實(shí)現(xiàn) updateShouldNotify 方法。
之前說(shuō)到調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例,所以此處定義一個(gè)靜態(tài)的 of 方法,通過(guò)傳入的 context 獲取到最近的 InheriedDataWidget 實(shí)例。
3.InheriedDataWidget 的使用
InheriedDataWidget 使用起來(lái)也很簡(jiǎn)單,它本身也是一個(gè)控件,只要在任意一個(gè)頁(yè)面的子控件調(diào)用其構(gòu)造方法就行,這里我們定義一個(gè)形如的 Widget 樹(shù)。
WidgetA 是一個(gè) StatefulWidget 類型的控件,可以調(diào)用 setState 刷新,如果是繼承人 Stateless 類型的控件,那我們也可以通過(guò) Stream 或者其他方式刷新數(shù)據(jù),感興趣的請(qǐng)看[什么是 Stream? Dart
WidgetA1_1 類
WidgetA1_2 類
WidgetA1_3 類
當(dāng)我們點(diǎn)擊 floatingActionButton 的時(shí)候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都會(huì)更新 Person 的信息,而且每點(diǎn) floatingActionButton 一次, 當(dāng)我們點(diǎn)擊 floatingActionButton 的時(shí)候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都會(huì)更新 Person 的信息,而且每點(diǎn) floatingActionButton 一次,都會(huì)輸出:
如果我們?cè)噲D在和 WidgetA 的同一層級(jí)的兄弟節(jié)點(diǎn)去訪問(wèn) InheriedDataWidget 的 Person 數(shù)據(jù),是不行的,因?yàn)楦腹?jié)點(diǎn)中并沒(méi)有插入 InheriedDataWidget。
把 WidgetB 和 WidgetA 保持同一節(jié)點(diǎn)
這也體現(xiàn)了 Inheried(遺傳) 這一單詞的特性,遺傳只存在于父子。兄弟不存在遺傳的關(guān)系。
這種數(shù)據(jù)共享的方式在某些場(chǎng)景還是很有用的,就比如說(shuō)全局主題,字體大小,字體顏色的變更,只要在 App 根層級(jí)共享出這些配置數(shù)據(jù),然后在觸發(fā)數(shù)據(jù)改變之后,所有引用到這些共享數(shù)據(jù)的地方都會(huì)刷新,這換主題,字體是不是就很輕松,事實(shí)上 Theme.of(context).primaryColor 之流就是這么干的。
以上就是有關(guān)InheritedWidget的使用。
自己也是從事Android開(kāi)發(fā)5年有余了;整理了一些Android開(kāi)發(fā)技術(shù)核心筆記和面經(jīng)題綱,有關(guān)更多Android開(kāi)發(fā)進(jìn)階技術(shù)資料、面經(jīng)題綱、核心技術(shù)筆記; 想要進(jìn)階自己、拿高薪的同學(xué)請(qǐng)私信我回復(fù)“核心筆記”或“面試”領(lǐng)?。?/p>
Flutter狀態(tài)管理系列:
Flutter狀態(tài)管理(一):ScopedModel
Flutter狀態(tài)管理(二):Provider
Flutter狀態(tài)管理(三):BLoC(Business Logic Component)
Flutter狀態(tài)管理(四):ReactiveX之RxDart
Flutter狀態(tài)管理(五):Redux
有做過(guò)H5前端開(kāi)發(fā)的朋友應(yīng)該很早就接觸過(guò)這個(gè),Redux在React/VUE中,與在Flutter/Dart中概念一樣,沒(méi)有任何區(qū)別;唯一的區(qū)別只是使用上的不同。
它主要由三部分組成:
下圖是一個(gè)完整的數(shù)據(jù)觸發(fā)及更新流程:
我們看到上面整個(gè)數(shù)據(jù)流,都是單向的,由View發(fā)起,最后到View的更新;
為啥這樣設(shè)計(jì)?
小節(jié)二介紹了Redux最基本的原理,但是,如何用Redux來(lái)做一些異步操作,比如:加載數(shù)據(jù)、請(qǐng)求API等?這里就引出來(lái)了Redux的中間件(Middleware),中間件能夠讓我們使得action在到達(dá)reducer之前,做些其它“動(dòng)作”!有了中間件,我們不但可以請(qǐng)求API,還可以改變action,使得分發(fā)到其它reducer中去;
上圖是有Middleware的流程圖。
Redux在Flutter中的使用與在JavaScript中的使用方式稍微有點(diǎn)不同,為啥?
因?yàn)镴avaScript是弱類型語(yǔ)言,而Dart是強(qiáng)類型語(yǔ)言,這就使得在JS中每個(gè)reducer可以獨(dú)立管理,而在Flutter中需要由一個(gè)大對(duì)象來(lái)管理!
無(wú)論在JS中還是在Flutter中,通常都將action、reducer、store各自建一目錄,放在redux目錄下,目錄結(jié)構(gòu)如下:
ReduxPage在build中,也可以直接用StoreBuilder(參考ReduxPage2中寫法),因?yàn)镾toreBuilder也是InheritedWidget。
正因?yàn)镽edux在Flutter中與在JS中不同,因此,在Flutter中,建議:
場(chǎng)景:多個(gè)組件共用一個(gè)狀態(tài),子組件通過(guò)方法改變父組件狀態(tài)
思路:狀態(tài)和管理方法定義在父組件,通過(guò)構(gòu)造函數(shù)傳遞給子組件
其他子組件按照同樣方法接收即可共用該父組件的狀態(tài)。
最近公司做技術(shù)分享寫的文章的demo
Flutter中的InheritedWidget狀態(tài)管理
1.InheritedWidget是什么?
InheritedWidget是Flutter中非常重要的一個(gè)功能型組件,它提供了一種數(shù)據(jù)在widget樹(shù)中從上到下傳遞、共享的方式,比如我們?cè)趹?yīng)用的根widget中通過(guò)InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget中來(lái)獲取該共享的數(shù)據(jù)!這個(gè)特性在一些需要在widget樹(shù)中共享數(shù)據(jù)的場(chǎng)景中非常方便!如Flutter SDK中正是通過(guò)InheritedWidget來(lái)共享應(yīng)用主題(Theme)和Locale (當(dāng)前語(yǔ)言環(huán)境)信息的。
InheritedWidget和React中的context功能類似,和逐級(jí)傳遞數(shù)據(jù)相比,它們能實(shí)現(xiàn)組件跨級(jí)傳遞數(shù)據(jù)。InheritedWidget的在widget樹(shù)中數(shù)據(jù)傳遞方向是從上到下的,這和通知Notification的傳遞方向正好相反。
2.源碼分析
InheritedWidget
先來(lái)看下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內(nèi)除了實(shí)現(xiàn)了createElement方法外沒(méi)有其他操作了,它的實(shí)現(xiàn)關(guān)鍵一定就是InheritedElement了。
InheritedElement 來(lái)看下InheritedElement源碼
class?InheritedElement?extends?ProxyElement { ??InheritedElement(InheritedWidget widget) :?super(widget);??@override??InheritedWidget?get?widget?=?super.widget;??// 這個(gè)Set記錄了所有依賴的Elementfinal?MapElement,?Object?_dependents?=?HashMapElement,?Object();
//該方法會(huì)在Element mount和activate方法中調(diào)用,_inheritedWidgets為基類Element中的成員,用于提高Widget查找父節(jié)點(diǎn)中的InheritedWidget的效率,它使用HashMap緩存了該節(jié)點(diǎn)的父節(jié)點(diǎn)中所有相關(guān)的InheritedElement,因此查找的時(shí)間復(fù)雜度為o(1) ??@override??void?_updateInheritance() {final?MapType,?InheritedElement incomingWidgets =?_parent?._inheritedWidgets;if?(incomingWidgets !=?null)??????_inheritedWidgets?=?HashMapType,?InheritedElement.from(incomingWidgets);????else??????_inheritedWidgets?=?HashMapType,?InheritedElement();????_inheritedWidgets[widget.runtimeType] =?this;??}
//該方法在父類ProxyElement中調(diào)用,看名字就知道是通知依賴方該進(jìn)行更新了,這里首先會(huì)調(diào)用重寫的updateShouldNotify方法是否需要進(jìn)行更新,然后遍歷_dependents列表并調(diào)用didChangeDependencies方法,該方法內(nèi)會(huì)調(diào)用mardNeedsBuild,于是在下一幀繪制流程中,對(duì)應(yīng)的Widget就會(huì)進(jìn)行rebuild,界面也就進(jìn)行了更新 ??@override??void?notifyClients(InheritedWidget oldWidget) {????assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for?(Element dependent?in?_dependents.keys) {??????notifyDependent(oldWidget,?dependent);????}??}
其中_updateInheritance方法在基類Element中的實(shí)現(xiàn)如下:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
總結(jié)來(lái)說(shuō)就是Element在mount的過(guò)程中,如果不是InheritedElement,就簡(jiǎn)單的將緩存指向父節(jié)點(diǎn)的緩存,如果是InheritedElement,就創(chuàng)建一個(gè)緩存的副本,然后將自身添加到該副本中,這樣做會(huì)有兩個(gè)值得注意的點(diǎn):
InheritedElement的父節(jié)點(diǎn)們是無(wú)法查找到自己的,即InheritedWidget的數(shù)據(jù)只能由父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞,反之不能。
如果某節(jié)點(diǎn)的父節(jié)點(diǎn)有不止一個(gè)同一類型的InheritedWidget,調(diào)用inheritFromWidgetOfExactType獲取到的是離自身最近的該類型的InheritedWidget。
看到這里似乎還有一個(gè)問(wèn)題沒(méi)有解決,依賴它的Widget是在何時(shí)被添加到_dependents這個(gè)列表中的呢?
回憶一下從InheritedWidget中取數(shù)據(jù)的過(guò)程,對(duì)于InheritedWidget有一個(gè)通用的約定就是添加static的of方法,該方法中通過(guò)inheritFromWidgetOfExactType找到parent中對(duì)應(yīng)類型的的InheritedWidget的實(shí)例并返回,與此同時(shí),也將自己注冊(cè)到了依賴列表中,該方法的實(shí)現(xiàn)位于Element類,實(shí)現(xiàn)如下:
@overrideT?dependOnInheritedWidgetOfExactType
// 這里通過(guò)上述mount過(guò)程中建立的HashMap緩存找到對(duì)應(yīng)類型的InheritedElement final?InheritedElement ancestor =?_inheritedWidgets?==?null???null?:?_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);
// 這個(gè)列表記錄了當(dāng)前Element依賴的所有InheritedElement,用于在當(dāng)前Element deactivate時(shí),將自己從InheritedElement的_dependents列表中移除,避免不必要的更新操作 ??_dependencies???=?HashSetInheritedElement();??_dependencies.add(ancestor);??ancestor.updateDependencies(this,?aspect);return?ancestor.widget;}
3.如何使用InheritedWidget
1)、創(chuàng)建一個(gè)類繼承自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.dependOnInheritedWidgetOfExactTypeInheritedContext();??}??@override??bool?updateShouldNotify(InheritedContext oldWidget) {????return?inheritedTestModel?!= oldWidget.inheritedTestModel;??}}
2)、InheritedTestModel類為數(shù)據(jù)容器(這里定義了一個(gè)Listint數(shù)據(jù)源)
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)、定義一個(gè)Widget?使用?InheritedContext類的數(shù)據(jù)?InheritedTestModel?
class?ListDemo?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState();??}}class?ListDemoState?extends?StateListDemo{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中通過(guò)Timer更新InheritedTestModel?中的數(shù)據(jù),然后再下一個(gè)Widget中獲取更新的數(shù)據(jù)作為展示
class?ListDemo2?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState2();??}}class?ListDemoState2?extends?StateListDemo2{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中更新數(shù)據(jù),子View不需任何操作直接從數(shù)據(jù)容器InheritedTestModel?中獲取到更新后的新數(shù)據(jù)
這是一個(gè)數(shù)據(jù)共享的簡(jiǎn)單的例子,基本的流程,大致就是A去更新B的數(shù)據(jù),A和B有一個(gè)共同的父類,實(shí)現(xiàn)數(shù)據(jù)的共享
4.上面說(shuō)了原理和基本的使用,但是在實(shí)際項(xiàng)目當(dāng)中,我當(dāng)然不建議這樣來(lái)使用,Google?已經(jīng)為我們封裝好了功能更加強(qiáng)大的插件Provider,其內(nèi)部原理就是基于InheritedWidget來(lái)實(shí)現(xiàn)的,我們理解了基本原理,可以更好的在項(xiàng)目中運(yùn)用Provider