狀態管理是聲明式編程很是重要的一個概念,咱們在前面介紹過Flutter是聲明式編程的,也區分聲明式編程和命令式編程的區別。vue
這裏,咱們就來系統的學習一下Flutter聲明式編程中很是重要的狀態管理git
不少從命令式編程框架(Android或iOS原生開發者)轉成聲明式編程(Flutter、Vue、React等)剛開始並不適應,由於須要一個新的角度來考慮APP的開發模式。github
Flutter做爲一個現代的框架,是聲明式編程的:web
在編寫一個應用的過程當中,咱們有大量的State須要來進行管理,而正是對這些State的改變,來更新界面的刷新:算法
某些狀態只須要在本身的Widget中使用便可編程
這種狀態咱們只須要使用StatefulWidget對應的State類本身管理便可,Widget樹中的其它部分並不須要訪問這個狀態。redux
這種方式在以前的學習中,咱們已經應用過很是屢次了。數據結構
開發中也有很是多的狀態須要在多個部分進行共享app
這種狀態咱們若是在Widget之間傳遞來、傳遞去,那麼是無窮盡的,而且代碼的耦合度會變得很是高,牽一髮而動全身,不管是代碼編寫質量、後期維護、可擴展性都很是差。框架
這個時候咱們能夠選擇全局狀態管理的方式,來對狀態進行統一的管理和應用。
開發中,沒有明確的規則去區分哪些狀態是短時狀態,哪些狀態是應用狀態。
可是咱們能夠簡單遵照下面這幅流程圖的規則:
針對React使用setState仍是Redux中的Store來管理狀態哪一個更好的問題,Redux的issue上,Redux的做者Dan Abramov,它這樣回答的:
The rule of thumb is: Do whatever is less awkward
經驗原則就是:選擇可以減小麻煩的方式。
InheritedWidget和React中的context功能相似,能夠實現跨組件數據的傳遞。
定義一個共享數據的InheritedWidget,須要繼承自InheritedWidget
class HYDataWidget extends InheritedWidget {
final int counter; HYDataWidget({this.counter, Widget child}): super(child: child); static HYDataWidget of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(HYDataWidget oldWidget) { return this.counter != oldWidget.counter; } } 複製代碼
建立HYDataWidget,而且傳入數據(這裏點擊按鈕會修改數據,而且從新build)
class HYHomePage extends StatefulWidget {
@override _HYHomePageState createState() => _HYHomePageState(); } class _HYHomePageState extends State<HYHomePage> { int data = 100; @override Widget 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中使用共享的數據,而且監聽
Provider是目前官方推薦的全局狀態管理工具,由社區做者Remi Rousselet 和 Flutter Team共同編寫。
使用以前,咱們須要先引入對它的依賴,截止這篇文章,Provider的最新版本爲4.0.4:
dependencies:
provider: ^4.0.4 複製代碼
在使用Provider的時候,咱們主要關心三個概念:
咱們先來完成一個簡單的案例,將官方計數器案例使用Provider來實現:
第一步:建立本身的ChangeNotifier
咱們須要一個ChangeNotifier來保存咱們的狀態,因此建立它
class CounterProvider extends ChangeNotifier {
int _counter = 100; int get counter { return _counter; } set counter(int value) { _counter = value; notifyListeners(); } } 複製代碼
第二步:在Widget Tree中插入ChangeNotifierProvider
咱們須要在Widget Tree中插入ChangeNotifierProvider,以便Consumer能夠獲取到數據:
void main() {
runApp(ChangeNotifierProvider( create: (context) => CounterProvider(), child: MyApp(), )); } 複製代碼
第三步:在首頁中使用Consumer引入和修改狀態
class HYHomePage extends StatelessWidget {
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表測試"), ), body: Center( child: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return Text("當前計數:${counterPro.counter}", style: TextStyle(fontSize: 20, color: Colors.red),); } ), ), floatingActionButton: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), ); } } 複製代碼
Consumer的builder方法解析:
步驟四:建立一個新的頁面,在新的頁面中修改數據
class SecondPage extends StatelessWidget {
@override Widget 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), ), ); } } 複製代碼
事實上,由於Provider是基於InheritedWidget,因此咱們在使用ChangeNotifier中的數據時,咱們能夠經過Provider.of的方式來使用,好比下面的代碼:
Text("當前計數:${Provider.of<CounterProvider>(context).counter}",
style: TextStyle(fontSize: 30, color: Colors.purple), ), 複製代碼
咱們會發現很明顯上面的代碼會更加簡潔,那麼開發中是否要選擇上面這種方式了?
爲何呢?由於Consumer在刷新整個Widget樹時,會盡量少的rebuild Widget。
方式一:Provider.of的方式完整的代碼:
class HYHomePage extends StatelessWidget {
@override Widget build(BuildContext context) { print("調用了HYHomePage的build方法"); return Scaffold( appBar: AppBar( title: Text("Provider"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text("當前計數:${Provider.of<CounterProvider>(context).counter}", style: TextStyle(fontSize: 30, color: Colors.purple), ) ], ), ), floatingActionButton: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), ); } } 複製代碼
方式二:將Text中的內容採用Consumer的方式修改以下:
Consumer<CounterProvider>(builder: (ctx, counterPro, child) {
print("調用Consumer的builder"); return Text( "當前計數:${counterPro.counter}", style: TextStyle(fontSize: 30, color: Colors.red), ); }), 複製代碼
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對比,不一樣之處主要是三個關鍵點:
這個時候,咱們從新測試點擊floatingActionButton,floatingActionButton中的代碼並不會進行rebuild操做。
因此在某些狀況下,咱們可使用Selector來代替Consumer,性能會更高。
在開發中,咱們須要共享的數據確定不止一個,而且數據之間咱們須要組織到一塊兒,因此一個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之間嵌套
runApp(ChangeNotifierProvider( create: (context) => CounterProvider(), child: ChangeNotifierProvider( create: (context) => UserProvider(), child: MyApp() ), )); 複製代碼
方式二:使用MultiProvider
runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: (ctx) => CounterProvider()), ChangeNotifierProvider(create: (ctx) => UserProvider()), ], child: MyApp(), )); 複製代碼
備註:全部內容首發於公衆號,以後除了Flutter也會更新其餘技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些本身的學習心得等,歡迎你們關注