flutter 局部状态和全局状态区别_Flutter状态管理
Flutter狀態管理
狀態管理是聲明式編程非常重要的一個概念,我們在前面介紹過Flutter是聲明式編程的,也區分聲明式編程和命令式編程的區別。這里,我們就來系統的學習一下Flutter聲明式編程中非常重要的狀態管理
一. 為什么需要狀態管理?
1.1. 認識狀態管理
很多從命令式編程框架(Android或iOS原生開發者)轉成聲明式編程(Flutter、Vue、React等)剛開始并不適應,因為需要一個新的角度來考慮APP的開發模式。
Flutter作為一個現代的框架,是聲明式編程的:
在編寫一個應用的過程中,我們有大量的State需要來進行管理,而正是對這些State的改變,來更新界面的刷新:
1.2. 不同狀態管理分類
1.2.1. 短時狀態Ephemeral state
某些狀態只需要在自己的Widget中使用即可
- 比如我們之前做的簡單計數器counter
- 比如一個PageView組件記錄當前的頁面
- 比如一個動畫記錄當前的進度
- 比如一個BottomNavigationBar中當前被選中的tab
這種狀態我們只需要使用StatefulWidget對應的State類自己管理即可,Widget樹中的其它部分并不需要訪問這個狀態。
這種方式在之前的學習中,我們已經應用過非常多次了。
1.2.2. 應用狀態App state
開發中也有非常多的狀態需要在多個部分進行共享
- 比如用戶一個個性化選項
- 比如用戶的登錄狀態信息
- 比如一個電商應用的購物車
- 比如一個新聞應用的已讀消息或者未讀消息
這種狀態我們如果在Widget之間傳遞來、傳遞去,那么是無窮盡的,并且代碼的耦合度會變得非常高,牽一發而動全身,無論是代碼編寫質量、后期維護、可擴展性都非常差。
這個時候我們可以選擇全局狀態管理的方式,來對狀態進行統一的管理和應用。
1.2.3. 如何選擇不同的管理方式
開發中,沒有明確的規則去區分哪些狀態是短時狀態,哪些狀態是應用狀態。
- 某些短時狀態可能在之后的開發維護中需要升級為應用狀態。
但是我們可以簡單遵守下面這幅流程圖的規則:
針對React使用setState還是Redux中的Store來管理狀態哪個更好的問題,Redux的issue上,Redux的作者Dan Abramov,它這樣回答的:
The rule of thumb is: Do whatever is less awkward
經驗原則就是:選擇能夠減少麻煩的方式。
二. 共享狀態管理
2.1. InheritedWidget
InheritedWidget和React中的context功能類似,可以實現跨組件數據的傳遞。
定義一個共享數據的InheritedWidget,需要繼承自InheritedWidget
- 這里定義了一個of方法,該方法通過context開始去查找祖先的HYDataWidget(可以查看源碼查找過程)
- updateShouldNotify方法是對比新舊HYDataWidget,是否需要對更新相關依賴的Widget
創建HYDataWidget,并且傳入數據(這里點擊按鈕會修改數據,并且重新build)
class HYHomePage extends StatefulWidget {@override_HYHomePageState createState() => _HYHomePageState(); }class _HYHomePageState extends State<HYHomePage> {int data = 100;@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("InheritedWidget"),),body: HYDataWidget(counter: data,child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[HYShowData()],),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.add),onPressed: () {setState(() {data++;});},),);} }在某個Widget中使用共享的數據,并且監聽
2.2. Provider
Provider是目前官方推薦的全局狀態管理工具,由社區作者Remi Rousselet 和 Flutter Team共同編寫。
使用之前,我們需要先引入對它的依賴,截止這篇文章,Provider的最新版本為4.0.4:
dependencies:provider: ^4.0.42.2.1. Provider的基本使用
在使用Provider的時候,我們主要關心三個概念:
- ChangeNotifier:真正數據(狀態)存放的地方
- ChangeNotifierProvider:Widget樹中提供數據(狀態)的地方,會在其中創建對應的ChangeNotifier
- Consumer:Widget樹中需要使用數據(狀態)的地方
我們先來完成一個簡單的案例,將官方計數器案例使用Provider來實現:
第一步:創建自己的ChangeNotifier
我們需要一個ChangeNotifier來保存我們的狀態,所以創建它
- 這里我們可以使用繼承自ChangeNotifier,也可以使用混入,這取決于概率是否需要繼承自其它的類
- 我們使用一個私有的_counter,并且提供了getter和setter
- 在setter中我們監聽到_counter的改變,就調用notifyListeners方法,通知所有的Consumer進行更新
第二步:在Widget Tree中插入ChangeNotifierProvider
我們需要在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以獲取到數據:
- 將ChangeNotifierProvider放到了頂層,這樣方便在整個應用的任何地方可以使用CounterProvider
第三步:在首頁中使用Consumer引入和修改狀態
- 引入位置一:在body中使用Consumer,Consumer需要傳入一個builder回調函數,當數據發生變化時,就會通知依賴數據的Consumer重新調用builder方法來構建;
- 引入位置二:在floatingActionButton中使用Consumer,當點擊按鈕時,修改CounterNotifier中的counter數據;
Consumer的builder方法解析:
- 參數一:context,每個build方法都會有上下文,目的是知道當前樹的位置
- 參數二:ChangeNotifier對應的實例,也是我們在builder函數中主要使用的對象
- 參數三:child,目的是進行優化,如果builder下面有一顆龐大的子樹,當模型發生改變的時候,我們并不希望重新build這顆子樹,那么就可以將這顆子樹放到Consumer的child中,在這里直接引入即可(注意我案例中的Icon所放的位置)
步驟四:創建一個新的頁面,在新的頁面中修改數據
class SecondPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("第二個頁面"),),floatingActionButton: Consumer<CounterProvider>(builder: (ctx, counterPro, child) {return FloatingActionButton(child: child,onPressed: () {counterPro.counter += 1;},);},child: Icon(Icons.add),),);} }2.2.2. Provider.of的弊端
事實上,因為Provider是基于InheritedWidget,所以我們在使用ChangeNotifier中的數據時,我們可以通過Provider.of的方式來使用,比如下面的代碼:
Text("當前計數:${Provider.of<CounterProvider>(context).counter}",style: TextStyle(fontSize: 30, color: Colors.purple), ),我們會發現很明顯上面的代碼會更加簡潔,那么開發中是否要選擇上面這種方式了?
- 答案是否定的,更多時候我們還是要選擇Consumer的方式。
為什么呢?因為Consumer在刷新整個Widget樹時,會盡可能少的rebuild Widget。
方式一:Provider.of的方式完整的代碼:
- 當我們點擊了floatingActionButton時,HYHomePage的build方法會被重新調用。
- 這意味著整個HYHomePage的Widget都需要重新build
方式二:將Text中的內容采用Consumer的方式修改如下:
- 你會發現HYHomePage的build方法不會被重新調用;
- 設置如果我們有對應的child widget,可以采用上面案例中的方式來組織,性能更高;
2.2.3. Selector的選擇
Consumer是否是最好的選擇呢?并不是,它也會存在弊端
- 比如當點擊了floatingActionButton時,我們在代碼的兩處分別打印它們的builder是否會重新調用;
- 我們會發現只要點擊了floatingActionButton,兩個位置都會被重新builder;
- 但是floatingActionButton的位置有重新build的必要嗎?沒有,因為它是否在操作數據,并沒有展示;
- 如何可以做到讓它不要重新build了?使用Selector來代替Consumer
我們先直接實現代碼,在解釋其中的含義:
floatingActionButton: Selector<CounterProvider, CounterProvider>(selector: (ctx, provider) => provider,shouldRebuild: (pre, next) => false,builder: (ctx, counterPro, child) {print("floatingActionButton展示的位置builder被調用");return FloatingActionButton(child: child,onPressed: () {counterPro.counter += 1;},);},child: Icon(Icons.add), ),Selector和Consumer對比,不同之處主要是三個關鍵點:
- 關鍵點1:泛型參數是兩個
- 泛型參數一:我們這次要使用的Provider
- 泛型參數二:轉換之后的數據類型,比如我這里轉換之后依然是使用CounterProvider,那么他們兩個就是一樣的類型
- 關鍵點2:selector回調函數
- 轉換的回調函數,你希望如何進行轉換
- S Function(BuildContext, A) selector
- 我這里沒有進行轉換,所以直接將A實例返回即可
- 關鍵點3:是否希望重新rebuild
- 這里也是一個回調函數,我們可以拿到轉換前后的兩個實例;
- bool Function(T previous, T next);
- 因為這里我不希望它重新rebuild,無論數據如何變化,所以這里我直接return false;
這個時候,我們重新測試點擊floatingActionButton,floatingActionButton中的代碼并不會進行rebuild操作。
所以在某些情況下,我們可以使用Selector來代替Consumer,性能會更高。
2.2.4. MultiProvider
在開發中,我們需要共享的數據肯定不止一個,并且數據之間我們需要組織到一起,所以一個Provider必然是不夠的。
我們在增加一個新的ChangeNotifier
import 'package:flutter/material.dart';class UserInfo {String nickname;int level;UserInfo(this.nickname, this.level); }class UserProvider extends ChangeNotifier {UserInfo _userInfo = UserInfo("why", 18);set userInfo(UserInfo info) {_userInfo = info;notifyListeners();}get userInfo {return _userInfo;} }如果在開發中我們有多個Provider需要提供應該怎么做呢?
方式一:多個Provider之間嵌套
- 這樣做有很大的弊端,如果嵌套層級過多不方便維護,擴展性也比較差
方式二:使用MultiProvider
runApp(MultiProvider(providers: [ChangeNotifierProvider(create: (ctx) => CounterProvider()),ChangeNotifierProvider(create: (ctx) => UserProvider()),],child: MyApp(), )); 備注:所有內容首發于公眾號,之后除了Flutter也會更新其他技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些自己的學習心得等,歡迎大家關注總結
以上是生活随笔為你收集整理的flutter 局部状态和全局状态区别_Flutter状态管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双屏鼠标经常跑到副屏_1+1gt;2,让
- 下一篇: 怎么买创业板股票