日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit)

發布時間:2024/4/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

目錄

生命周期

State改變時組件如何刷新

InheritedWidget

InheritedModel

InheritedNotifier

Notifier


生命周期

flutter的生命周期其實有兩種:StatefulWidget和StatelessWidget。

這兩個是flutter的兩個基本組件,名稱已經很好表明了這兩個組件的功能:有狀態和無狀態。

(1)StatelessWidget

StatelessWidget是無狀態組件,它的生命周期非常簡單,只有一個build,如下:

class WidgetA extends StatelessWidget {@overrideWidget build(BuildContext context) {return ...;} }

對于StatelessWidget來說只渲染一次,之后它就不再有任何改變。

由于無狀態組件在執行過程中只有一個 build 階段,在執行期間只會執行一個 build 函數,沒有其他生命周期函數,因此在執行速度和效率方面比有狀態組件更好。所以在設計組件時,要考慮業務情況,盡量使用無狀態組件。

(2)StatefulWidget

StatelessWidget是有狀態組件,我們討論的生命周期也基本指它的周期,如圖:

包含以下幾個階段:

  • createState?

    該函數為 StatefulWidget 中創建 State 的方法,當 StatefulWidget 被調用時會立即執行 createState 。

  • initState?

    該函數為 State 初始化調用,因此可以在此期間執行 State 各變量的初始賦值,同時也可以在此期間與服務端交互,獲取服務端數據后調用 setState 來設置 State。

  • didChangeDependencies

    該函數是在該組件依賴的 State 發生變化時,這里說的 State 為全局 State ,例如語言或者主題等,類似于前端 Redux 存儲的 State 。

  • build?
    主要是返回需要渲染的 Widget ,由于 build 會被調用多次,因此在該函數中只能做返回 Widget 相關邏輯,避免因為執行多次導致狀態異常,注意這里的性能問題。

  • reassemble

    主要是提供開發階段使用,在 debug 模式下,每次熱重載都會調用該函數,因此在 debug 階段可以在此期間增加一些 debug 代碼,來檢查代碼問題。

  • didUpdateWidget

    該函數主要是在組件重新構建,比如說熱重載,父組件發生 build 的情況下,子組件該方法才會被調用,其次該方法調用之后一定會再調用本組件中的 build 方法。

  • deactivate

    在組件被移除節點后會被調用,如果該組件被移除節點,然后未被插入到其他節點時,則會繼續調用 dispose 永久移除。

  • dispose

    永久移除組件,并釋放組件資源。

在StatelessWidget中,只要我們調用setState,就會執行重繪,也就是說重新執行build函數,這樣就可以改變ui。

State改變時組件如何刷新

先來看看下方的代碼:

class MyHomePage extends StatefulWidget {@override_MyHomePageState createState() => _MyHomePageState(); }class _MyHomePageState extends State<MyHomePage> {int _counter = 0;void _incrementCounter() {setState(() {_counter++;});}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [WidgetA(_counter),WidgetB(),WidgetC(_incrementCounter)],),),);} }class WidgetA extends StatelessWidget {final int counter;WidgetA(this.counter);@overrideWidget build(BuildContext context) {return Center(child: Text(counter.toString()),);} }class WidgetB extends StatelessWidget {@overrideWidget build(BuildContext context) {return Text('I am a widget that will not be rebuilt.');} }class WidgetC extends StatelessWidget {final void Function() incrementCounter;WidgetC(this.incrementCounter);@overrideWidget build(BuildContext context) {return RaisedButton(onPressed: () {incrementCounter();},child: Icon(Icons.add),);} }

我們有三個Widget,一個負責顯示count,一個按鈕改變count,一個則是靜態顯示文字,通過這三個Widget來對比比較頁面的刷新邏輯。

上面代碼中,三個Widget是在_MyHomePageState的build中創建的,執行后點擊按鈕可以發現三個Widget都刷新了。

在Flutter Performance面板上選中Track Widget Rebuilds即可看到

雖然三個Widget都是無狀態的StatelessWidget,但是因為_MyHomePageState的State改變時會重新執行build函數,所以三個Widget會重新創建,這也是為什么WidgetA雖然是無狀態的StatelessWidget卻依然可以動態改變的原因。

所以:無狀態的StatelessWidget并不是不能動態改變,只是在其內部無法通過State改變,但是其父Widget的State改變時可以改變其構造參數使其改變。實際上確實不能改變,因為是一個新的實例。

下面我們將三個組件提前創建,可以在_MyHomePageState的構造函數中創建,修改后代碼如下:

class _MyHomePageState extends State<MyHomePage> {int _counter = 0;List<Widget> children;_MyHomePageState(){children = [WidgetA(_counter),WidgetB(),WidgetC(_incrementCounter)];}void _incrementCounter() {setState(() {_counter++;});}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: children,),),);} }

再次執行,發現點擊沒有任何效果,Flutter Performance上可以看到沒有Widget刷新(這里指三個Widget,當然Scaffold還是刷新了)。

這是因為組件都提前創建了,所以執行build時沒有重新創建三個Widget,所以WidgetA顯示的內容并沒有改變,因為它的counter沒有重新傳入。

所以,不需要動態改變的組件可以提前創建,build時直接使用即可,而需要動態改變的組件實時創建。

這樣就可以實現局部刷新了么?我們繼續改動代碼如下:

class _MyHomePageState extends State<MyHomePage> {int _counter = 0;Widget b = WidgetB();Widget c ;_MyHomePageState(){c = WidgetC(_incrementCounter);}void _incrementCounter() {setState(() {_counter++;});}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [WidgetA(_counter),b,c],),),);} }

我們只將WidgetB和WidgetC重新創建,而WidgetA則在build中創建。執行后,點擊按鈕WidgetA的內容改變了,查看Flutter Performance可以看到只有WidgetA刷新了,WidgetB和WidgetC沒有刷新。

所以:通過提前創建靜態組件build時直接使用,而build時直接創建動態Widget 這種方式可以實現局部刷新。

注意:

只要setState,_MyHomePageState就會刷新,所以WidgetA就會跟著刷新,即使count沒有改變。比如上面代碼中將setState中的_count++代碼注釋掉,再點擊按鈕雖然內容沒有改變,但是WidgetA依然刷新。

這種情況可以通過InheritedWidget來進行優化。

InheritedWidget

InheritedWidget的作用什么?網上有人說是數據共享,有人說是用于局部刷新。我們看官方的描述:

Base?class?for?widgets?that?efficiently?propagate?information?down?the?tree.

可以看到它的作用是Widget樹從上到下有效的傳遞消息,所以很多人理解為數據共享,但是注意這個“有效的”,這個才是它的關鍵,而這個有效的其實就是解決上面提到的問題。

那么它怎么使用?

先創建一個繼承至InheritedWidget的類:

class MyInheriteWidget extends InheritedWidget{final int count;MyInheriteWidget({@required this.count, Widget child}) : super(child: child);static MyInheriteWidget of(BuildContext context){return context.dependOnInheritedWidgetOfExactType<MyInheriteWidget>();}@overridebool updateShouldNotify(MyInheriteWidget oldWidget) {return oldWidget.count != count;} }

這里將count傳入。重點注意要實現updateShouldNotify函數,通過名字可以知道這個函數決定InheritedWidget的Child Widget是否需要刷新,這里我們判斷如果與之前改變了才刷新。這樣就解決了上面提到的問題。

然后還要實現一個static的of方法,用于Child Widget中獲取這個InheritedWidget,這樣就可以訪問它的count屬性了,這就是消息傳遞,即所謂的數據共享(因為InheritedWidget的child可以是一個layout,里面有多個widget,這些widget都可以使用這個InheritedWidget中的數據)。

然后我們改造一下WidgetA:

class WidgetA extends StatelessWidget {@overrideWidget build(BuildContext context) {final MyInheriteWidget myInheriteWidget = MyInheriteWidget.of(context);return Center(child: Text(myInheriteWidget.count.toString()),);} }

這次不用在構造函數中傳遞count了,直接通過of獲取MyInheriteWidget,使用它的count即可。

最后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {int _counter = 0;Widget a = WidgetA();Widget b = WidgetB();Widget c ;_MyHomePageState(){c = WidgetC(_incrementCounter);}void _incrementCounter() {setState(() {_counter++;});}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [MyInheriteWidget(count: _counter,child: a,),b,c],),),);} }

注意,這里用MyInheriteWidget包裝一下WidgetA,而且WidgetA必須提前創建,如果在build中創建則每次MyInheriteWidget刷新都會跟著刷新,這樣updateShouldNotify函數的效果就無法達到。

執行,點擊按鈕,可以發現只有WidgetA刷新了(當然MyInheriteWidget也刷新了)。如果注釋掉setState中的_count++代碼,再執行并點擊發現雖然MyInheriteWidget刷新了,但是WidgetA并不刷新,因為MyInheriteWidget的count并未改變。

下面我們改動一下代碼,將WidgetB和C都放入MyInheriteWidget會怎樣?

@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [MyInheriteWidget(count: _counter,child: Column(children: [a,b,c],),),],),),);} }

MyInheriteWidget的child是一個Column,將a、b、c都放在這下面。執行會發現依然是WidgetA刷新,B和C都不刷新。這是因為在B和C中沒有執行MyInheriteWidget的of函數,就沒有執行dependOnInheritedWidgetOfExactType,這樣其實就沒構成依賴,MyInheriteWidget就不會通知它們。

如果我們修改WidgetC,在build函數中添加一行MyInheriteWidget.of(context);那么雖然沒有任何使用,依然能會跟著刷新,因為建立了依賴關系就會被通知。

InheritedWidget會解決多余的刷新問題,比如在一個頁面中有多個屬性,同樣有多個Widget來使用這些屬性,但是并不是每個Widget都使用所有屬性。如果用最普通的實現方式,那么每次setState(無論改變哪個屬性)都需要刷新這些Widget。但是如果我們用多個InheritedWidget來為這些Widget分類,使用相同屬性的用同一個InheritedWidget來包裝,并實現updateShouldNotify,這樣當改變其中一個屬性時,只有該屬性相關的InheritedWidget才會刷新它的child,這樣就提高了性能。

InheritedModel

InheritedModel是繼承至InheritedWidget的,擴充了它的功能,所以它的功能更加強大。具體提現在哪里呢?

通過上面我們知道,InheritedWidget可以通過判斷它的data是否變化來決定是否刷新child,但是實際情況下這個data可以是多個變量或者一個復雜的對象,而child也不是單一widget,而是一系列widget組合。比如展示一本書,數據可能有書名、序列號、日期等等,但是每個數據可能單獨變化,如果用InheritedWidget,就需要每種數據需要一個InheritedWidget類,然后將使用該數據的widget包裝,這樣才能包裝改變某個數據時其他widget不刷新。

但是這樣的問題就是widget層級更加復雜混亂,InheritedModel就可以解決這個問題。InheritedModel最大的功能就是根據不同數據的變化刷新不同的widget。下面來看看如何實現。

首先創建一個InheritedModel:

class MyInheriteModel extends InheritedModel<String>{final int count1;final int count2;MyInheriteModel({@required this.count1, @required this.count2, Widget child}) : super(child: child);static MyInheriteModel of(BuildContext context, String aspect){return InheritedModel.inheritFrom(context, aspect: aspect);}@overridebool updateShouldNotify(MyInheriteModel oldWidget) {return count1 != oldWidget.count1 || count2 != oldWidget.count2;}@overridebool updateShouldNotifyDependent(MyInheriteModel oldWidget, Set<String> dependencies) {return (count1 != oldWidget.count1 && dependencies.contains("count1")) ||(count2 != oldWidget.count2 && dependencies.contains("count2"));} }

這里我們傳入兩個count,除了實現updateShouldNotify方法,還需要實現updateShouldNotifyDependent方法。這個函數就是關鍵,可以看到我們判斷某個數據是否變化后還判斷了dependencies中是否包含一個關鍵詞:

count1 != oldWidget.count1 && dependencies.contains("count1")

這個關鍵詞是什么?從哪里來?后面會提到,這里先有個印象。

然后同樣需要實現一個static的of函數來獲取這個InheritedModel,不同的是這里獲取的代碼變化了:

InheritedModel.inheritFrom(context, aspect: aspect);

這里的aspect就是后面用到的關鍵字,而inheritFrom會將這個關鍵字放入dependencies,以便updateShouldNotifyDependent來使用。后面會詳細解釋這個aspect完整作用。

然后我們改造WidgetA:

class WidgetA extends StatelessWidget {@overrideWidget build(BuildContext context) {final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count1");return Center(child: Text(myInheriteModel.count1.toString()),);} }

可以看到,這里定義了aspect。

然后因為有兩個count,所以我們再新增兩個Widget來處理count2:

class WidgetD extends StatelessWidget {@overrideWidget build(BuildContext context) {final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count2");return Center(child: Text(myInheriteModel.count2.toString()),);} }class WidgetE extends StatelessWidget {final void Function() incrementCounter;WidgetE(this.incrementCounter);@overrideWidget build(BuildContext context) {return RaisedButton(onPressed: () {incrementCounter();},child: Icon(Icons.add),);} }

這里可以看到WidgetD的aspect與WidgetA是不同的。

最后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {int _counter = 0;int _counter2 = 0;Widget a = Row(children: [WidgetA(),WidgetD()],);Widget b = WidgetB();Widget c ;Widget e ;_MyHomePageState(){c = WidgetC(_incrementCounter);e = WidgetE(_incrementCounter2);}void _incrementCounter() {setState(() {_counter++;});}void _incrementCounter2() {setState(() {_counter2++;});}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [MyInheriteModel(count1: _counter,count2: _counter2,child: a,),b,c,e],),),);} }

WidgetD和E是處理count2的,A和C則是處理count。而MyInheriteModel的child不是單一Widget,而是一個Row,包含WidgetD和A。

執行代碼,可以發現點擊WidgetC的時候,只有WidgetA刷新了(當然MyInheriteModel也刷新);而點擊WidgetD的時候,只有WidgetE刷新了。這樣我們就實現了MyInheriteModel中的局部刷新。

其實原理很簡單,aspect就相當于一個標記,當我們通過InheritedModel.inheritFrom(context, aspect: aspect);獲取MyInheriteModel時,實際上將本Widget依賴到MyInheriteModel,并且將這個Widget標記。這時候如果data改變,遍歷它的所有依賴時,會通過每個依賴的Widget獲取它對應的標記集dependencies,然后觸發updateShouldNotifyDependent判斷該Widget是否刷新。

所以在InheritedModel(其實是InheritedElement)中存在一個map,記錄了每個依賴的Widget對應的dependencies,所以一個Widget可以有多個標記,因為dependencies是一個Set,這樣就可以響應多個數據的變化(比如多個數據組成一個String作為文本顯示)。

上面其實可以用兩個InheritedWidget也可以實現,但是布局越復雜,就需要越多的InheritedWidget,維護起來也費時費力。

所以可以看到InheritedModel使用更靈活,功能更強大,更適合復雜的數據和布局使用,并且通過細分細化每一個刷新區域,使得每次刷新都只更新最小區域,極大的提高了性能。

InheritedNotifier

InheritedNotifier同樣繼承至InheritedWidget,它是一個給Listenable的子類的專用工具,它的構造函數中要傳入一個Listenable(這是一個接口,不再是之前的各種數據data),比如動畫(如AnimationController),然后其依賴的組件則根據Listenable進行更新。

首先還是先創建一個InheritedNotifier:

class MyInheriteNotifier extends InheritedNotifier<AnimationController>{MyInheriteNotifier({Key key,AnimationController notifier,Widget child,}) : super(key: key, notifier: notifier, child: child);static double of(BuildContext context){return context.dependOnInheritedWidgetOfExactType<MyInheriteNotifier>().notifier.value;} }

這里提供的of函數則直接返回AnimationController的value即可。

然后創建一個Widget:

class Spinner extends StatelessWidget {@overrideWidget build(BuildContext context) {return Transform.rotate(angle: MyInheriteNotifier.of(context) * 2 * pi,child: Text("who!!"),);} }

內容會根據AnimationController進行旋轉。

修改WidgetA:

class WidgetA extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("WidgetA"),);} }

然后修改_MyHomePageState:

class _MyHomePageState extends State<MyHomePage6> with SingleTickerProviderStateMixin {AnimationController _controller;@overridevoid initState() {super.initState();_controller = AnimationController(vsync: this,duration: Duration(seconds: 10),)..repeat();}@overrideWidget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [WidgetA(),MyInheriteNotifier(notifier: _controller,child: Spinner()),],),),);} }

運行會看到Text在不停的旋轉,當然如果有其他Widget可以看到并不跟著刷新。

總之InheritedNotifier是一個更細化的工具,聚焦到一個具體場景中,使用起來也更方便。

Notifier

最后再簡單介紹一下Notifier,考慮一個需求:頁面A是列表頁,而頁面B是詳情頁,兩個頁面都有點贊操作和顯示點贊數量,需要在一個頁面點贊后兩個頁面的數據同時刷新。這種情況下就可以使用flutter提供另外一種方式——Notifier。

Notifier其實就是訂閱模式的實現,主要包含ChangeNotifier和ValueNotifier,使用起來也非常簡單。通過addListener和removeListener進行訂閱和取消訂閱(參數是無參無返回值的function),當數據改變時調用notifyListeners();通知即可。

ValueNotifier是更簡單的ChangeNotifier,只有一個數據value,可以直接進行set和get,set時自動執行notifyListeners(),所以適合單數據的簡單場景。

當時注意Notifier只是共享數據并通知變化,并不實現刷新,所以還要配合其他一并實現。比如上面的InheritedNotifier(因為Notifier都繼承Listenable接口,所以兩個可以很簡單的配合使用),或者第三方庫Provider(web開發的習慣)等等。

源碼

關注公眾號:BennuCTech,發送“Inherite1”獲取源碼。

?

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。