狀態管理必要性
Flutter基于聲明式構建UI,原生則是命令式,狀態管理是用于解決聲明式開發帶來的問題。
例:命令式的原生,數據更新需要拿到對應控件并更改其顯示值;而聲明式則需要更改數據值并通過setstate更新狀態,重新構建組件
Flutter 中有這么一種說法: UI = f(state):
聲明式的優勢
-
優勢:
-
無需繁瑣地控制組件,只需聚焦于狀態管理,負責狀態—>UI的映射
-
劣勢:
-
邏輯和頁面UI耦合,導致無法復用/單元測試、修改混亂等:MVVM等架構解決
-
跨頁面訪問數據
-
控制頁面刷新范圍
provider工作原理
provider內部為DelegateWidget(委托組件)是一個StatefulWidget,可更新,具有生命周期,借助各種代理完成
狀態共享使用InheritedProvider這個InheritedWidget實現
通過MultiProvider和Consumer封裝,對組合與刷新顆粒度控制
provider工作流程:
設置到changeNotifierProvider的changeNotifier被執行addListener添加監聽listener
listener內會調用StateDelegate的StateSetter方法,從而調用到StatefulWidget的setState
在changeNotifier執行notifyListeners時,最終觸發setState更新
provider異同
- ListenableProvider / ChangeNotifierProvider
ListenableProvider提供的對象是繼承了Listenable抽象類的子類,只能通過繼承來實現addListener/removeListener方法,手動管理收聽者
changeNotifier實現了Listenable,而混入了changeNotifier的類自動實現了監聽管理
ChangeNotifierProvider 和 ListenableProvider 究竟區別在哪呢,ChangeNotifierProvider 會在你需要的時候,自動調用其 _disposer 方法。
-
ValueListenableProvider,提供了繼承/混入/實現了ValueListenable的model,專門用于只有一個單一變化數據的ChangeNotifier,通過ValueListenable處理的類不再需要數據更新時調用notifyListeners。
-
StreamProvider,專門提供一條Single Stream,提供了方法捕獲異常、更新數據、構建流、構建流控制器等
狀態同步
- 獲取頂層數據:flutter在每個element上維護一個InheritedWidget哈希表來向下傳遞element樹中的信息,通常情況下,多個element引用相同的哈希表,并且該表僅在element引入新的InheritedWidget時改變, 時間復雜度為O(1)。
- 通知刷新:listener模式,model中維護聽眾,并通過notifiedListener通知刷新,全局狀態需放在頂層之上,優先初始化
數據初始化
- 全局數據:main方法執行,保證只執行一次
- 單頁面數據:StatefulWidget中的InitState中不可執行Provider.of(context),當監聽后,在notifyListeners的時候,會觸發context所對應的State的[State.build]和[State.didChangeDependencies]方法,數據到來時又會觸發下一次請求,無限請求下去。
解決頁面和邏輯的耦合
思路:
- 通過flutter樹機制解決,如provider
- 通過依賴注入,如Get
通過flutter樹機制處理V—>P的獲取
flutter三棵樹:widget、element、render object
widget樹是虛擬結構,只是描述組件嵌套關系,但element和renderObject在運行時實際存在。element組件中包含了_parent屬性,存放其父節點element,而其又實現了buildContext接口,包含了對樹結構操作的方法
原本應該是通過context.findAncestorStateOfType向上獲取父組件的信息,在有了provider之后通過provider.of(context)向上獲取頂層provider組件中的presenter對象
通過依賴注入解決V—>P的獲取
擺脫context依賴,基于get借助一個全局單例的map存儲對象,通過依賴注入的方式,實現對Presenter層的獲取,使得可在任意類中獲取到Presenter
map對應的key是runtimeType+tag,其中tag為可選參數,value對應object
get也可解決跨頁面訪問數據
避免setstate全局更新
觀察者模式,局部更新
- ValueNotifier、ValueListenableBuilder
- ChangeNotifierProvider、ChangeNotifier、Consumer:從頂層ChangeNotifierProvider獲取存儲的ChangeNotifier,Consumer作為子組件獲取對應數據
Get對應方式則是Get.put和GetBuilder,Get.put提前存儲數據對象,為GetBuilder組件指定數據類型作為泛型,因為Get基于單例,所以GetBuilder可以直接通過泛型獲取到存入的對象,在builder方法中暴露,使得組件和數據建立了監聽關系,并在數據更新后只驅動將其作為泛型的GetBuilder組件更新
使用缺陷
- provider的context層級過高,如provider傳入的context是根層級的,而provider在element樹中是根層級下面
解決:對應組件外嵌套一層builder,拿到該結點對應的context / provider作為根結點
- Get全局單例
Get全局單例默認以runtimeType為key進行對象存儲,而不同詳情頁實例對應的是同一個class,key值一樣,不添加tag參數,在Get.find時會獲取到已經存儲的對象,即數據混淆了。
Get存儲的對象也得回收,dipose時進行delete或者使用Get中提供的組件,如GetBuilder,會在dispose中釋放