Flutter|一文搞懂何謂狀態管理
目
錄
1.什麼是狀態管理
2.不同的狀態管理分類
3.Flutter中的有狀態元件和無狀態元件
4.Flutter中有哪些可以做到狀態管理
5.為什麼要使用狀態管理
6.常見的狀態管理框架有哪些
7.狀態管理總結&思考
01
什麼是狀態管理
隨著“大前端”概念流行的同時,響應式程式設計的理念也隨之被越來越多的人所瞭解和學習,要了解和學習響應式的程式設計框架就一定離不開狀態管理,客戶端Android、iOS是通過明確的命令式指令去控制我們的UI變化,如setText,而在響應式程式設計下,我們只需要描述好UI和狀態之間的關係,然後專注於狀態的改變就好了,框架會根據狀態的變化來自動更新UI。
總結來說就是:狀態管理就是當某個狀態發生改變的時候,告知使用該狀態的狀態監聽者,讓狀態所監聽的屬性隨知改變,從而達到聯動效果。
02
不同的狀態管理分類
-
短時狀態Ephemeral state
某些狀態、或是可以理解為某些資料只需要在當前的Widget中訪問和使用,不需要對這些狀態進行共享訪問,你需要的只是一個StatefulWidget元件,依靠這個StatefulWidget元件自己的State類自己管理即可,不需要使用狀態管理框架去管理這種狀態,這些狀態可以稱之為短時狀態。
如:官網中的計數器Demo、比如一個PageView元件記錄當前的頁面
-
應用狀態App state
某些狀態需要被元件共享訪問,當這個狀態發生變化的時候,其他元件也需要隨之發生聯動的變化,這就是應用狀態。
舉個例子來說明,比如一個電商App,在商品的詳情頁面,我們把某個商品加入了購物車,那麼商品是否放入購物車這個狀態,就需要被購物車頁面元件所訪問,那麼這個狀態就是應用狀態。
試想一下,如果再不使用第三方狀態管理框架的情況下,我們可以怎麼實現呢,可以使用InheritedWidget定向的傳遞,可以通過Notification進行通知,可以使用event_bus來進行事件訂閱等等,其實我們所說的狀態管理框架,也是基於上面說的等幾種方式來實現的。
總結來說,要區分短時狀態還是應用狀態,就看這個狀態需不需要被多個元件進行訪問,當這個狀態一發生變化,其他元件需要隨之發生聯動變化,就是應用狀態,反之,其他元件不需要變化、不受影響,就是短時狀態。
03
Flutter中的有狀態元件和無狀態元件
在Flutter中,元件根據狀態分為,有狀態元件StatefulWidget和無狀態元件StatelessWidget。
StatelessWidget:無狀態的Widget,它無法通過setState設定元件狀態進行重繪,它內的屬性應該被宣告為final,防止改變。
StatefulWidget:有狀態的Widget,建立一個StatefulWidget元件時,它同時建立一個State物件,通過與State關聯可以達到重新整理UI的目的。
State:在Flutter中,Widget和State具有不同的生命週期,Widget是臨時物件,用於構建當前狀態下的應用程式,而State物件在多次呼叫build()之間保持不變,允許它們儲存資訊(狀態)。
State生命週期:
04
Flutter中有哪些可以做到狀態管理
State
常用而且使用最頻繁的一個狀態管理類,它必須結合StatefulWidget一起使用,StreamBuilder繼承自StatefulWidget,同樣是通過setState來管理狀態
State缺點:
-
無法做到跨元件共享資料(這個跨是無關聯的,如果是直接的父子關係,我們不認為是跨元件) setState是State的函式,一般我們會將State的子類設定為私有,所以無法做到讓別的元件呼叫State的setState函式來重新整理。
-
setState會成為維護的難點,因為啥哪哪都是。隨著頁面狀態的增多,你可能在呼叫setState的地方會越來越多,不能統一管理。
-
處理資料邏輯和檢視混合在一起,違反程式碼設計原則 比如資料庫的資料取出來setState到Ui上,這樣編寫程式碼,導致狀態和UI耦合在一起,不利於測試,不利於複用。
-
setState是整個Widget重新構建(而且子Widget也會跟著銷燬重建),如果頁面足夠複雜,就會導致嚴重的效能損耗。建議使用StreamBuilder,原理上也是State,但它做到了子Widget的區域性重新整理,不會導致整個頁面的重建。
InheritedWidget
它的天生特性就是能繫結InheritedWidget與依賴它的子孫元件的依賴關係,並且當InheritedWidget資料發生變化時,可以自動更新依賴的子孫元件!
利用這個特性,我們可以將需要跨元件共享的狀態儲存在InheritedWidget中,然後在子元件中引用InheritedWidget即可。
專門負責Widget樹中資料共享的功能型Widget,如Provider、scoped_model就是基於它開發的。
InheritedWidget缺點:
-
每次更新都會通知所有的子Widget,無法定向通知/指向性通知,容易造成不必要的重新整理。
-
不支援跨頁面(route)的狀態,意思是跨樹,如果不在一個樹中,我們無法獲取。
-
資料是不可變的,必須結合StatefulWidget、ChangeNotifier或者Steam使用。
Notification
它是Flutter中跨層資料共享的一種機制,注意,它不是widget,它提供了dispatch方法,沿著context對應的Element節點向上逐層傳送通知
Notification缺點:
-
不支援跨頁面(route)的狀態,準確說不支援NotificationListener同級或者父級Widget的狀態通知。
-
本身不支援重新整理UI,需要結合State使用。
-
如果結合State,會導致整個UI的重繪,效率底下不科學。
Stream
純Dart的實現,跟Flutter沒什麼關係,扯上關係的就是用StreamBuilder來構建一個Stream通道的Widget,像知名的rxdart、BloC、flutter_redux、fish_redux全都用到了Stream的api。
Stream 缺點:
-
api生澀,不好理解。
-
需要定製化,才能滿足更復雜的場景。
-
缺點恰恰是它的優點,保證了足夠靈活,你更可基於它做一個好的設計,滿足當下業務的設計。
05
為什麼要使用狀態管理
對於不需要傳遞的狀態或者不需要共享的狀態,我們不需要進行復雜的狀態管理,單純依靠setState也可以很好的完成我們的需求。
但是隨著產品迭代節奏速度的加快,專案逐漸變得越來越龐大,不同元件之間的資料依賴性越來越高,我們就需要更清晰、明確的處理各個元件之間的資料關係,這時候如果還單單使用setState做狀態處理,我們就很難明確的處理資料的流向,最終可能會導致資料傳遞和巢狀邏輯過於複雜,不便於維護和管理,在出現問題的時候,也會花費大量的時間成本來捋清資料之間的關係。
總的來說,對於跨元件(跨頁面)之間進行資料共享和傳遞,而且需要保持狀態的一致性和可維護性,這就需要我們對狀態進行管理。
06
常見的狀態管理框架有哪些
Provider
-
Provider是官方文件的例子用的方法. Google 比較推薦的用法. 和BLoC的流式思想相比, Provider是一個觀察者模式, 狀態改變時要notifyListeners().
-
Provider的實現在內部還是利用了InheritedWidget,允許將有效資訊傳遞到元件樹下的小元件. Provider的好處: dispose指定後會自動被呼叫, 支援MultiProvider.
-
Provider從名字上就很容易理解,它就是用於提供資料,無論是在單個頁面還是在整個app 都有它自己的解決方案,可以很方便的管理狀態。
-
常用概念:
-
ChangeNotifier:系統提供的被觀察者,資料model需要繼承
-
Provider:訂閱者,只用於資料共享管理,提供給子孫節點使用,UpdateShouldNotify Function,用於控制重新整理時機
-
ChangeNotifierProvider:訂閱者,不僅能夠提供資料供子孫節點使用,還可以在資料改變的時候通知所有消費者。Model變化後會自動通知ChangeNotifierProvider(訂閱者),ChangeNotifierProvider內部會重新構建InheritedWidget,而依賴該InheritedWidget的子孫Widget就會更新.
-
MultiProvider:多個訂閱者:實際上就是通過每一個provider都實現了的 cloneWithChild方法把自己一層一層包裹起來。
-
Consumer:消費者,能夠在複雜專案中,極大地縮小你的控制元件重新整理範圍。最多支援6中model
-
Selector: 消費者,強化的Consumer,支援過濾重新整理
-
使用流程:
-
新增依賴
-
建立資料 Model
-
建立頂層共享資料
-
頂層Provider包裹
-
在子頁面中獲取狀態
-
Provder種類:
-
Provider:只能提供恆定的資料,不能通知依賴它的子部件重新整理。
-
ListenableProvider: 提供的物件是繼承了 Listenable 抽象類的子類,必須實現其 addListener / removeListener 方法,通常不需要。
-
ChangeNotifierProvider: 對子節點提供一個繼承/混入/實現了ChangeNotifier的類,只需要在Model中with ChangeNotifier ,然後在需要重新整理狀態時呼叫 notifyListeners 即可。
-
ValueListenableProvider: 提供實現了繼承/混入/實現了ValueListenable的Model,實際上是專門用於處理只有一個單一變化資料的ChangeNotifier。
-
StreamProvider: 專門用作提供(provide)一條 Single Stream。
-
FutureProvider:提供了一個 Future 給其子孫節點,並在 Future 完成時,通知依賴的子孫節點進行重新整理。
-
總結:
本質上:Prvioder通過inheritedElement實現區域性重新整理,通過控制自己實現的Element層來更新UI,通過Element提供的unmount函式回撥dispose,實現選擇性釋放,
其核心類:InheritedProvider
Provider不僅做到了提供資料,而且它擁有著一套完整的解決方案,覆蓋了你會遇到的絕大多數情況。就連BLoC未解決的那個棘手的dispose問題,和ScopedModel的侵入性問題,它也都解決了。它能夠讓你開發出簡單、高效能、層次清 的應用。
不足之處:Flutter Widget 構建模式很容易在UI層面上元件化,但是僅僅使用Provider,Model和 View之間還是容易產生依賴。只有通過手動將Model轉化為ViewModel這樣才能消除掉依賴關係。
Redux
Redux是一種單向資料流架構,可以輕鬆開發,維護和測試應用程式,也是google推薦的狀態管理方式。
-
原理
-
所有的狀態都儲存在Store裡。這個Store會放在根Widget.
-
View拿到Store的狀態資料會對映成檢視渲染.
-
Redux不直接讓view操作資料,通過dispatch一個action通知Reducer,狀態變更
-
Reducer接收到這個action,根據action狀態,生成新的狀態,並替換在Store的舊狀態.
-
Store儲存了新的狀態後,就通知所有使用到了這個狀態的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態了.
-
Redux相關概念
-
State:資料model
-
Store 倉庫:整個APP的頂層,儲存和管理state
-
Action 動作:通過發起一個Action來告訴Reducer該更新狀態了
-
Reducer 還原:根據Action產生新的狀態
-
StoreProvider: 一個InheritedWidget,內部儲存了一個Store。(資料中心)最頂層必須是 StoreProvider 開始
-
StoreConnector: 聯結器:需要兩個泛型
1)一個是我們建立的 State(ReduxState)
2)一個是 ViewModel,ViewModel決定了converter(轉換函式)那邊的返回值型別
同時提供了一個StoreStreamListener,本質上是一個StreamBuilder -
StoreConverter:轉換器:類似於Selector中的selector,轉換成本Widget想要的資料
-
StoreStreamListener: 通過監聽自己的Stream來完成檢視的重建。
-
StoreBuilder:功能同StoreConnector,StoreConnector主要是有個資料轉化的作用,可以對資料先做一些轉化操作再賦值到元件上,StoreBuilder是直接將資料給顯示在元件上
-
middleware 中介軟體:類似攔截器,作用域位於reducer更新狀態之前,本質上也是一個函式。
比如當前是新增使用者動作,但是我想在新增使用者這操作的前面再做一步其他的動作(非同步 action ,action 過濾,日誌輸出,異常報告等),這時候就可以使用中介軟體middleware,實現MiddlewareClass該類就行。 -
中介軟體的call方法中有個關鍵方法next(),大多數情況需要呼叫,否則中介軟體的鏈條斷了,後面的中介軟體和Reducer就不執行了。
-
Dispatcher:如何通知狀態更新呢?通過store.dispatch
-
Redux頁面重新整理流程
-
Redux使用流程:
-
新增依賴
-
建立State
-
建立action
-
建立reducer
-
建立store
-
將Store放入頂層
-
在子頁面中獲取Store中的state
-
發出action
-
優點:
-
自動訂閱
-
自動通知
-
可以定向通知
-
檢視和業務邏輯分離
-
Redux 的缺點:
-
Redux 核心僅僅關心資料管理,不關心具體什麼場景來使用它,這是它的優點同時也是它的缺點.
-
在我們實際使用 Redux 中面臨兩個具體問題.
-
Redux 的集中和 Component 的分治之間的矛盾.
-
Redux 的 Reducer 需要一層層手動組裝,帶來的繁瑣性和易錯性.
GetX
GetX是Flutter上的一個輕量且強大的解決方案,包括但不限於:
-
高效的狀態管理。
-
便捷的路由管理。
-
豐富的Api。
-
GetX的三項基本原則:
-
效能:GetX專注於效能和最小資源消耗,GetX打包後的apk佔用大小和執行時的記憶體佔用與其他狀態管理外掛不相上下。
-
效率:GetX的語法非常便捷,並保持了極高的效能,能極大縮短你的開發時長。
-
結構:GetX可以將介面、邏輯、依賴和路由完全解藕,用起來更清爽,邏輯更清晰,程式碼更容易維護。
-
GetX高效的狀態管理:
之所以說GetX是高效的狀態管理,是因為他不需要堆疊大量的控制、管理程式碼(如Action、middleware、reducer、state),而且不具有侵入性,可以降低業務和檢視間的耦合度。
在使用上,使用GetX的響應式狀態管理就像使用setState一樣簡單(其實本質就是setState),並且GetX可以做到區域性重新整理。 -
使用GetX實現一個簡單的登陸功能
Demo邏輯:HomePage為主頁面,事件跳轉到登入頁面,登入頁面登入成功後關閉頁面,主頁面重新整理Text文案內容。
class HomePage extends StatelessWidget {
//例項化的Controller
final LoginController logic = Get.put(LoginController(), tag: 'login');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Center(
child: GestureDetector(
onTap: () {
Get.to(LoginPage());
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Obx(
() => Text(logic.loginStatus.value),
),
),
),
),
),
);
}
}
登入頁面
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//通過find方法,可以找到你已經例項化的Controller
final LoginController loginController = Get.find(tag: 'login');
return Scaffold(
appBar: AppBar(
title: Text('LoginPage'),
),
body: Center(
child: GestureDetector(
onTap: () {
loginController.login();
},
child: Container(
width: 200,
height: 200,
color: Colors.green,
child: Center(child: Text('點我登入')),
),
),
),
);
}
}
controller控制器
class LoginController extends GetxController{
///通過.obs將loginStatus標記為被觀察者
///loginStatus是RxString型別的,不是String型別
var loginStatus = '未登入'.obs;
login() => {
loginStatus.value = '已登入',
Get.back(),
// update()
};
}
-
通過Demo感受到GetX的優點
-
業務、檢視解藕,業務邏輯可以放在Controller中進行處理。
-
程式碼簡潔,無需建立大量的控制類。
-
區域性重新整理,當被觀察的資料發生變化時,只有觀察者部分會進行重新整理,不會整個頁面進行重新整理。
-
相同的方法(如login),如果被觀察的資料沒有發生變化,則不會進行區域性重新整理。
-
從此告別StatefulWidget。
-
更簡單的實現跨頁面互動事件。
07
狀態管理總結&思考
7.1 如何選擇框架
沒有哪一種框架可以適配所有的情況,也沒有一種框架可以永遠適用.
應該根據業務分析適合哪一種,當業務變化時,程式碼也需要跟著進化,以適配業務的發展.從一開始就介入fish_redux這樣的框架,成本高,難度大,只是為了實現一些簡單的二級,三級頁面,並不是一個好的選擇。
7.2 選型原則
-
侵入性
-
擴充套件性
-
高效能
-
安全性
-
駕馭性
-
易用性
-
範圍性
所有的框架都有侵入性,你同意嗎?
目前侵入性比較高的代表ScopedModel,如果你選擇的框架只能使用它提供的幾個入口,可以放棄使用它。
高效能:也是很重要的,這個需要明白它的原理,看它到底如何做的管理。
安全性:也很重要,看他資料管理通道是否安全穩定。
駕馭性:你說你都不理解你就敢用,出了問題找誰?如果駕馭不了也不要用。
易用性:大家應該都明白,如果用它一個框架需要N多配置,N多實現,放棄吧,不合適。簡單才是硬道理。
範圍性 :這個特點是flutter中比較明顯的,框架選型一定要考慮框架的適用範圍,到底是適合做區域性管理,還是適合全域性管理,要做一個實際的考量。
7.3 多種狀態管理框架是否可以同時使用?
當然可以,你用了redux,就不允許setstate()了? 顯然不是.如何同時使用不同的框架能滿足你的需求,使你的效能更好,使用更方便,可讀性更強那就使用吧。
- Flutter 動態化 | Flutter Dart 三端一體化動態化平臺實踐
- 超級全面的Flutter效能優化實踐
- 58同城 Android App啟動優化實踐
- Flutter|一文搞懂何謂狀態管理
- 新技術人性化 | 走向人機協作的VR鐳射拍攝工具設計
- 珊瑚海跨端解決方案及在移動端的佈局動態化實踐
- 開源 | WLock:高可用分散式鎖設計實踐
- iOS不必現崩潰的點對點解析以及治理
- 開源|WBBlades重要節點更新-專為提效而設計
- Flutter動態化 | Fair 2.5.0 新版本特性
- 強化學習在黃頁商家智慧聊天助手中的探索實踐
- AI面試機器人後端架構實踐
- 當我們聊定時器時,到底在聊什麼
- Flutter動態化 | Fair 2.4.0 新版本特性
- 乾貨|短視訊創意設計,爆款視訊手到擒來!
- 一種支援泛型解析的PHPScf無痕化技術方案
- 乾貨|短視訊創意設計,爆款視訊手到擒來!
- 分散式鎖實現原理解析(Redis & WLock)
- 一種支援泛型解析的PHPScf無痕化技術方案
- 低程式碼實時數倉構建系統的設計與實踐