Flutter 狀態管理之Provider
標簽: flutter
在flutter
中狀態管理是重中之重,每當談這個話題,總有說不完的話。
在正式介紹 Provider
為什么我們需要狀態管理。如果你已經對此十分清楚,那么建議直接跳過這一節。
如果我們的應用足夠簡單,Flutter
作為一個聲明式框架,你或許只需要將 數據 映射成 視圖 就可以了。你可能并不需要狀態管理,就像下面這樣。
但是隨著功能的增加,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。
這又是什么鬼。我們很難再清楚的測試維護我們的狀態,因為它看上去實在是太復雜了!而且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展示的時候,外部也需要顯示點贊數,這時候就需要同步這兩個狀態。
Flutter
實際上在一開始就為我們提供了一種狀態管理方式,那就是 StatefulWidget
。但是我們很快發現,它正是造成上述原因的罪魁禍首。
在 State
屬于某一個特定的 Widget
,在多個 Widget
之間進行交流的時候,雖然你可以使用 callback
解決,但是當嵌套足夠深的話,我們增加非常多可怕的垃圾代碼。
這時候,我們便迫切的需要一個架構來幫助我們理清這些關系,狀態管理框架應運而生。
Provider 是什么
通過使用
Provider
而不用手動編寫InhertedWidget,您將獲取自動分配、延遲加載、大大減少每次創建新類的代碼。
首先在yaml
中添加,具體版本號參考:官方Provider pub,當前版本號是4.1.3
.
Provider: ^4.1.3
然后運行
flutter pub get
獲取到最新的包到本地,在需要的文件夾內導入
import 'package:provider/provider.dart';
簡單例子
我們還用點擊按鈕新增數字的例子
首先創建存儲數據的Model
class ProviderModel extends ChangeNotifier {
int _count=0;
ProviderModel();
void plus() {
/// 在數據變動的時候通知監聽者刷新UI
_count = _count + 1;
notifyListeners();
}
}
構造view
/// 使用Consumer來監聽全局刷新UI
Consumer<ProviderModel>(
builder:
(BuildContext context, ProviderModel value, Widget child) {
print('Consumer 0 刷新');
_string += 'c0 ';
return _Row(
value: value._count.toString(),
callback: () {
context.read<ProviderModel>().plus();
},
);
},
child: _Row(
value: '0',
callback: () {
context.read<ProviderModel>().plus();
},
),
)
測試下看下效果:
單個Model多個小部件分別刷新(局部刷新)
單個model
實現單個頁面多個小部件分別刷新,是使用Selector<Model,int>
來實現,首先看下構造函數:
class Selector<A, S> extends Selector0<S> {
/// {@macro provider.selector}
Selector({
Key key,
@required ValueWidgetBuilder<S> builder,
@required S Function(BuildContext, A) selector,
ShouldRebuild<S> shouldRebuild,
Widget child,
}) : assert(selector != null),
super(
key: key,
shouldRebuild: shouldRebuild,
builder: builder,
selector: (context) => selector(context, Provider.of(context)),
child: child,
);
}
可以看到Selector
繼承了Selector0
,再看Selector
關鍵build
代碼:
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
T value;
Widget cache;
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
final selected = widget.selector(context);
var shouldInvalidateCache = oldWidget != widget ||
(widget._shouldRebuild != null &&
widget._shouldRebuild.call(value, selected)) ||
(widget._shouldRebuild == null &&
!const DeepCollectionEquality().equals(value, selected));
if (shouldInvalidateCache) {
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
return cache;
}
}
根據我們傳入的_shouldRebuild
來判斷是否需要更新,如果需要更新則執行widget.build(context,selected,child)
,否則返回已經緩存的cache
.當沒有_shouldRebuild
參數時則根據widget.selector(ctx)
的返回值判斷是否和舊值相等,不等則更新UI
。
所以我們不寫shouldRebuild
也是可以的。
局部刷新用法
Widget build(BuildContext context) {
print('page 1');
_string += 'page ';
return Scaffold(
appBar: AppBar(
title: Text('Provider 全局與局部刷新'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('全局刷新<Consumer>'),
Consumer<ProviderModel>(
builder:
(BuildContext context, ProviderModel value, Widget child) {
print('Consumer 0 刷新');
_string += 'c0 ';
return _Row(
value: value._count.toString(),
callback: () {
context.read<ProviderModel>().plus();
},
);
},
child: _Row(
value: '0',
callback: () {
context.read<ProviderModel>().plus();
},
),
),
SizedBox(
height: 40,
),
Text('局部刷新<Selector>'),
Selector<ProviderModel, int>(
builder: (ctx, value, child) {
print('Selector 1 刷新');
_string += 's1 ';
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Selector<Model,int>次數:' + value.toString()),
OutlineButton(
onPressed: () {
context.read<ProviderModel>().plus2();
},
child: Icon(Icons.add),
)
],
);
},
selector: (ctx, model) => model._count2,
shouldRebuild: (m1, m2) {
print('s1:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '數據相等,本次不刷新'}');
return m1 != m2;
},
),
SizedBox(
height: 40,
),
Text('局部刷新<Selector>'),
Selector<ProviderModel, int>(
selector: (context, model) => model._count3,
shouldRebuild: (m1, m2) {
print('s2:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '數據相等,本次不刷新'}');
return m1 != m2;
},
builder: (ctx, value, child) {
print('selector 2 刷新');
_string += 's2 ';
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Selector<Model,int>次數:' + value.toString()),
OutlineButton(
onPressed: () {
ctx.read<ProviderModel>().plus3();
},
child: Icon(Icons.add),
)
],
);
},
),
SizedBox(
height: 40,
),
Text('刷新次數和順序:↓'),
Text(_string),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
child: Icon(Icons.refresh),
onPressed: () {
setState(() {
_string += '\n';
});
},
),
OutlineButton(
child: Icon(Icons.close),
onPressed: () {
setState(() {
_string = '';
});
},
)
],
)
],
),
),
);
}
效果:
當我們點擊局部刷新s1
,執行s1
的build
,s1
不相等,s2
相等不刷新。輸出:
flutter: s2:5 5 數據相等,本次不刷新
flutter: s1:6 7 不相等,本次刷新
flutter: Selector 1 刷新
flutter: Consumer 0 刷新
當點擊s2
,s2
的值不相等刷新UI
,s1
數據相等,不刷新UI
.
flutter: s2:2 3 不相等,本次刷新
flutter: selector 2 刷新
flutter: s1:0 0 數據相等,本次不刷新
flutter: Consumer 0 刷新
可以看到上邊2次Consumer
每次都刷新了,我們探究下原因。
Consumer 全局刷新
Consumer
繼承了SingleCHildStatelessWidget
,當我們在ViewModel
中調用notification
則當前widget
被標記為dirty
,然后在build
中執行傳入的builder
函數,在下幀則會刷新UI
。
而Selector<T,S>
則被標記dirty
時執行_Selector0State
中的buildWithChild(ctx,child)
函數時,根據selected
和_shouldRebuild
來判斷是否需要執行widget.builder(ctx,selected,child)
(刷新UI
).
其他用法
多model寫法
只需要在所有需要model
的上級包裹即可,當我們一個page
需要2
個model
的時候,我么通常這樣子寫:
class BaseProviderRoute extends StatelessWidget {
BaseProviderRoute({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ProviderModel>(
create: (_) => ProviderModel(),
),
ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()),
],
child: BaseProvider(),
);
}
}
當然是用的時候和單一model
一致的。
Selector<ProviderModel2, int>(
selector: (context, model) => model.value,
builder: (ctx, value, child) {
print('model2 s1 刷新');
_string += 'm2s1 ';
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Selector<Model2,int>次數:' + value.toString()),
OutlineButton(
onPressed: () {
ctx.read<ProviderModel2>().add(2);
},
child: Icon(Icons.add),
)
],
);
},
),
watch && read
watch
源碼是Provider.of<T>(this)
,默認Provider.of<T>(this)
的listen=true
.
static T of<T>(BuildContext context, {bool listen = true}){
final inheritedElement = _inheritedElementOf<T>(context);
if (listen) {
context.dependOnInheritedElement(inheritedElement);
}
return inheritedElement.value;
}
而read
源碼是Provider.of<T>(this, listen: false)
,watch
/read
只是寫法簡單一點,并無高深結構。
當我們想要監聽值的變化則是用
watch
,當想調用model
的函數時則使用read
參考
智能推薦
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
猜你喜歡
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...
【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)
資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...
dataV組件容器寬高發生變化后,組件不會自適應解決方法
項目中需要大屏幕數據展示,于是使用了dataV組件,但是使用是發現拖動瀏覽器邊框,dataV組件顯示異常,如圖: 于是查了官網,官網的解釋如下: 于是按照官網的意思編寫代碼: 于是可以自適應了...