上一課時我詳細(xì)介紹了有/無狀態(tài)組件的應(yīng)用設(shè)計,但是在設(shè)計過程中,還缺乏一個對狀態(tài)管理的考慮,。本課時介紹狀態(tài)管理設(shè)計的必要性,,以及一些常見的狀態(tài)管理技術(shù)對比,最后再著重通過 Provider 來優(yōu)化前一課時中的例子,。
狀態(tài)管理場景
上一課時的例子中,,只涉及一個有狀態(tài)的組件 article_like_bar ,接下來我們需要實現(xiàn)另外一個詳情頁面,,并且在詳情頁面中也需要一個點(diǎn)贊功能,,具體的界面效果可以參考動圖 1 (為了界面更好,我在上一課時的基礎(chǔ)上增加了一些樣式),。
圖 1 增加二級點(diǎn)贊詳情頁面效果
在上面的動圖例子中,,你是否發(fā)現(xiàn)了一個問題?第一個頁面的點(diǎn)贊數(shù)與第二個頁面的點(diǎn)贊數(shù)并不同步,。在實際項目開發(fā)過中,,需求方希望二級詳情頁面的點(diǎn)贊數(shù)能與第一個頁面的點(diǎn)贊數(shù)同步。
如果不引入新的技術(shù)方案,,能想到的辦法就是將該狀態(tài)進(jìn)行提升,,放到其共同的父節(jié)點(diǎn)上,然后將父節(jié)點(diǎn)設(shè)計為有狀態(tài)組件,,并提供修改狀態(tài)的方法給到子組件,。可以用圖 2 來表示,。
圖 2 狀態(tài)提升共享方式
上面的方式是可以做到這點(diǎn),,但是你有沒有發(fā)現(xiàn),只因為一個點(diǎn)贊行為,,就需要將兩個頁面的所有組件(靜態(tài)組件和動圖組件)進(jìn)行重新 build ,,成本實在太高,這也違背了我們上一課時的組件設(shè)計原則(盡可能減少動態(tài)組件下的靜態(tài)組件),。為了更好地解決這個問題,,我們就需要引入一些狀態(tài)管理的方法,下面就介紹一些常見的技術(shù)方案,,同時做一個對比,。
狀態(tài)選型對比
狀態(tài)管理技術(shù)不少于 10 種,但是為了高效,,我只介紹其中比較核心的三個,,第一個是原生所使用的 InheritedWidget ;第二個是相對前端同學(xué)比較熟悉的 Redux 技術(shù),;最后一個則是我們推薦使用的技術(shù) Provider ,。
InheritedWidget
InheritedWidget 核心原理和狀態(tài)提升原理一致,,將 likeNum 提升到根節(jié)點(diǎn),但不需要一層層地將變量傳遞下去,,只需要在根節(jié)點(diǎn)聲明即可,。
現(xiàn)在我們有一個頁面,頁面下有兩個組件,,兩個組件都需要用同一個名字,,并且第二個組件的名字可以點(diǎn)擊切換隨機(jī)名字,而切換以后需要及時更新第一個組件中的名字,。頁面效果如圖 3 所示,。
圖 3 多組件狀態(tài)共享效果
按照上面介紹的例子以及上一課時的知識點(diǎn),畫一個簡單的組件樹,,并且附帶上需要的狀態(tài)屬性,,如圖 4 所示。
圖 4 InheritedWidget 組件設(shè)計
-
首先創(chuàng)建一個根結(jié)點(diǎn)為一個有狀態(tài)組件 name_game,;
-
name_game 為一個有狀態(tài)類,狀態(tài)屬性為 name,,并帶有 changName 的狀態(tài)修改方法,;
-
創(chuàng)建一個狀態(tài)管理類組件 NameInheritedWidget ;
-
創(chuàng)建 NameInheritedWidget 的三個子組件,,分別為 welcome(顯示歡迎 name ),、random_name(顯示 name ,并且有點(diǎn)擊切換隨機(jī) name 操作)和 other_widgets ,。
對于上面的結(jié)構(gòu),,肯定有很多同學(xué)比較疑惑,other_widgets 并沒有使用這個 name 狀態(tài),,為什么要在 NameInheritedWidget 下呢,?
帶著這樣的疑惑,我們先來看下 name_game 核心代碼(為了在專欄中更簡潔,,我省去了部分代碼,,完整代碼大家可以參考文章下的 github 代碼地址)。
復(fù)制代碼 /// 隨機(jī)名字游戲組件狀態(tài)管理類
class NameGameState extends State<NameGame> {
/// name 狀態(tài)
String name;
/// 構(gòu)造函數(shù)參數(shù),,避免父組件狀態(tài)變化,,而引起的子組件的重 build 操作
Widget child;
/// 修改當(dāng)前名字
void changeName() {
List<String> nameList = ['flutter one', 'flutter two', 'flutter three'];
int pos = Random().nextInt(3);
setState(() {
name = nameList[pos];
});
}
@override
void initState() {
setState(() {
name = 'test flutter';
});
super.initState();
}
/// 構(gòu)造函數(shù)
NameGameState()
{
child = Column (
children: <Widget>[
Welcome(),
RandomName(),
TestOther(),
]
);
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
NameInheritedWidget(
child: child,
onNameChange: changeName,
name: name
),
],
);
}
}
上面代碼中,定義狀態(tài)屬性 name ,,并創(chuàng)建了可以修改 state 的 changeName 方法,。接下來在 build 中使用 NameInheritedWidget 這個組件(該組件可以理解為前端所說的高階組件,也就是通過將組件作為參數(shù)傳遞進(jìn)該組件,,并返回一個新的組件的功能組件),,這個組件包裹了兩個需要狀態(tài) name 的組件( Welcome 和 RandomName )以及一個不需要狀態(tài)的 TestOther,。
上面代碼中還有一個比較特殊的地方,就是將 child 作為了 state ,,在構(gòu)造函數(shù)中進(jìn)行了定義,,并將該組件的所有子組件都包含在了 child 中。具體什么原因,,大家可以繼續(xù)往下學(xué)習(xí),。
接下來我們看一下 NameInheritedWidget 的實現(xiàn)邏輯,代碼如下:
復(fù)制代碼 import 'package:flutter/material.dart';
/// 定義一個name共享父組件
class NameInheritedWidget extends InheritedWidget {
/// 共享狀態(tài)
final String name;
/// 修改共享狀態(tài)方法
final Function onNameChange;
/// 構(gòu)造函數(shù)
NameInheritedWidget({
Key key,
@required Widget child,
@required this.name,
@required this.onNameChange,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(NameInheritedWidget old) =>
name != old.name;
}
主要是接受兩個參數(shù),, name 和 onNameChange 方法,,并且有一個判斷函數(shù) updateShouldNotify 。前面兩個參數(shù)不用介紹,,關(guān)鍵在于 updateShouldNotify ,,這個判斷函數(shù)的作用就是上面大家的疑惑點(diǎn)。
如果將 TestOther 不作為該子組件,,那么根據(jù)我們之前了解到的知識點(diǎn),,由于 setState 會觸發(fā)父組件 NameGame 的更新,而子組件會因為父組件的更新,,則會引發(fā)執(zhí)行 build 操作,。
如果 TestOther 是 NameInheritedWidget 的子組件,那么在執(zhí)行 setState 后,,NameInheritedWidget 會判斷狀態(tài)是否有狀態(tài)變化,,還會判斷子組件是否有依賴該 name 狀態(tài),從而就保證了兩點(diǎn):
-
狀態(tài)變化時,,如果未使用該狀態(tài)子組件,,則不會發(fā)生 build;
-
使用了該狀態(tài)組件,,如果組件的狀態(tài)沒有發(fā)生變化,,也不會發(fā)生 build。
這兩點(diǎn)就非常好地保護(hù)了我們剛開始提到的問題,,因為有狀態(tài)父組件的更新,,而導(dǎo)致全部子節(jié)點(diǎn)的 build 操作。這里要非常注意,,需要使用 NameGameState 方法來封裝組件,,如果該子組件直接寫在 build 中的 child 方法中,就無法利用 NameInheritedWidget 優(yōu)點(diǎn),,這點(diǎn)大家要特別注意,。
最后我們再來看下子組件如何利用 name 和 onNameChange 這兩個值,我們可以看下 RandomName 組件,,代碼如下:
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:two_you/inherited_widget/name_inherited_widget.dart';
/// 隨機(jī)展示人名
class RandomName extends StatelessWidget {
/// 有狀態(tài)類返回組件信息
@override
Widget build(BuildContext context) {
final String name = (
context.inheritFromWidgetOfExactType(NameInheritedWidget)
as NameInheritedWidget).name;
final Function changeName = (
context.inheritFromWidgetOfExactType(NameInheritedWidget)
as NameInheritedWidget).onNameChange;
return FlatButton(
child: Text(name),
onPressed: () => changeName(),
);
}
}
上面代碼中可以看到,,是通過以下方式來獲得 InheritedWidget 對象中的方法和屬性,。
復(fù)制代碼 context.inheritFromWidgetOfExactType(NameInheritedWidget) as NameInheritedWidget)
總結(jié)下 InheritedWidget 實現(xiàn)狀態(tài)管理的要點(diǎn):
-
狀態(tài)提升,將需要共享的狀態(tài)提升到共同且最近的一個父節(jié)點(diǎn),,并使用 InheritedWidget 來管理,;
-
該父節(jié)點(diǎn)上,將所有子節(jié)點(diǎn)作為該節(jié)點(diǎn)狀態(tài)管理類的一個構(gòu)造函數(shù)參數(shù),,并且傳遞給 InheritedWidget,;
-
子節(jié)點(diǎn)通過 inheritFromWidgetOfExactType 的方法來獲取狀態(tài)管理類 InheritedWidget 中的屬性以及方法。
Redux
由于 Redux 在前端是一個比較常用的狀態(tài)管理技術(shù)解決方案,,因此這里簡單介紹一下,,不過在 Flutter 中 ,Redux 并非第一選擇,。Redux 核心思想是單向數(shù)據(jù)流架構(gòu),,將所有的狀態(tài)存儲在 store 中,所有數(shù)據(jù)改變都是通過 Action ,,然后 Action 觸發(fā) store 存儲,,store 變化觸發(fā)所有應(yīng)用該狀態(tài)的組件的 build 操作。為了實現(xiàn)效果,,我們也同樣使用上面的例子,,步驟如下:
-
因為是第三方庫,因此需要在 pubspec.yaml 增加依賴,;
-
實現(xiàn) state 管理中心;
-
創(chuàng)建相應(yīng)的 Action ,,觸發(fā)狀態(tài)變化,;
-
創(chuàng)建相應(yīng)的 reduce;
-
將狀態(tài)添加到 store 中,,并放在 APP 最頂層,。
接下來我們一步步實現(xiàn)代碼邏輯。
這里單獨(dú)創(chuàng)建一個目錄 states ,,用于狀態(tài)管理,,其次在 states 目錄中創(chuàng)建 name_state.dart ,并實現(xiàn)其中的代碼如下,,創(chuàng)建相應(yīng)的 state 以及 Action,。
復(fù)制代碼 import 'dart:math';
/// name 狀態(tài)管理類
class NameStates {
final String _name;
/// getter 方法獲取name
get name => _name;
/// 構(gòu)造函數(shù)
NameStates(this._name);
/// 初始設(shè)置
NameStates.initState() : _name = 'test flutter 1';
}
/// 定義 name state 對應(yīng)的狀態(tài)修改 action
///
/// [NameActions.changeName] 為修改當(dāng)前 name
enum NameActions {
/// 修改 name 的 state
changeName
}
實現(xiàn)對應(yīng)的 Action 方法。
復(fù)制代碼 /// 修改當(dāng)前name,,隨機(jī)選取一個
NameStates changeName() {
List<String> nameList = ['flutter one', 'flutter two', 'flutter three'];
int pos = Random().nextInt(3);
return NameStates(nameList[pos]);
}
在 reducer 中增加對應(yīng) Action 的判斷,。
復(fù)制代碼 /// reducer 方法,觸發(fā)組件更新
NameStates reducer(NameStates state, action){
if (action == NameActions.changeName) {
return changeName();
}
return state;
}
上面就完成了整個 state 類管理,,這點(diǎn)和前端的 reducer 實現(xiàn)完全一致,。接下來我們看下,,在 APP 底層創(chuàng)建的代碼。
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:two_you/pages/name_game.dart';
import 'package:two_you/states/name_states.dart';
/// APP 核心入口文件
void main() {
final store =
Store<NameStates>(reducer, initialState: NameStates.initState());
runApp(MyApp(store));
}
/// MyApp 核心入口界面
class MyApp extends StatelessWidget {
/// 初始
final Store<NameStates> store;
/// 構(gòu)造函數(shù)
MyApp(this.store);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return StoreProvider<NameStates>(
store: store,
child: MaterialApp(
title: 'Two You', // APP 名字
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue, // APP 主題
),
home: Scaffold(
appBar: AppBar(
title: Text('Two You'), // 頁面名字
),
body: Center(
//child: HomePage(),
child: NameGame(store: store),
))),
);
}
}
在 main 函數(shù)中創(chuàng)建 store 對象并執(zhí)行初始化,,然后在具體需要使用 store 的方法中使用如下代碼規(guī)則:
復(fù)制代碼 return StoreProvider<NameStates>(
store: store,
child: (具體的組件,,可以直接使用 store 變量),
)
子組件如果需要使用 store ,也需要在子組件中聲明 store 變量作為組件參數(shù),,我們看下 RandomName 組件內(nèi)的使用和實現(xiàn),。
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:two_you/states/name_states.dart';
/// 隨機(jī)展示人名
class RandomName extends StatelessWidget {
/// store
final Store store;
/// 構(gòu)造函數(shù)
RandomName({Key key, this.store}) : super(key: key);
/// 有狀態(tài)類返回組件信息
@override
Widget build(BuildContext context) {
print('random name build');
return StoreConnector<NameStates,String>(
converter: (store) => store.state.name.toString(),
builder: (context, name) {
return StoreConnector<NameStates,VoidCallback>(
converter: (store) {
return () => store.dispatch(NameActions.changeName);
},
builder: (context, callback) {
return FlatButton(
child: Text(name),
onPressed: () => callback(),
);
}
);
},
);
}
}
這種方式就需要層層傳遞這個 store ,從而會顯得代碼非常臃腫,,特別是上面代碼中的 19 行和 22 行,。你會發(fā)現(xiàn),如果需要的 Action 越多,,StoreConnector 的層級就越深,,你就會陷入深深的代碼嵌套中。
當(dāng)然使用 redux ,,并不會因為父組件的更新而導(dǎo)致子組件的 build 問題,,其他部分詳細(xì)的代碼,大家可參考 github 源碼,。
Provider
最后我們來看下官方推薦的技術(shù)方案 Provider ,,開發(fā)過程比較簡單,分為三步:
-
創(chuàng)建狀態(tài)管理類 name_model ,,創(chuàng)建對應(yīng)的狀態(tài) name 以及其修改 name 的方法 changeName,;
-
在 name_game 中增加 provider 的支持,并將相應(yīng)需要共享的組件使用 provider 進(jìn)行封裝,,監(jiān)聽數(shù)據(jù)變化,;
-
在子組件中獲取 provider 的 name 數(shù)據(jù)以及 changeName 方法,在相應(yīng)的點(diǎn)擊部分觸發(fā) changeName 事件,。
在使用 Provider 來實現(xiàn)狀態(tài)管理,,我們需要創(chuàng)建一個 model 文件夾,放入對應(yīng)的狀態(tài)類 name_model ,,代碼實現(xiàn)如下:
復(fù)制代碼 import 'dart:math';
import 'package:flutter/material.dart';
/// name狀態(tài)管理模塊
class NameModel with ChangeNotifier {
/// 聲明私有變量
String _name = 'test flutter';
/// 設(shè)置get方法
String get value => _name;
/// 修改當(dāng)前name,,隨機(jī)選取一個
void changeName() {
List<String> nameList = ['flutter one', 'flutter two', 'flutter three'];
int pos = Random().nextInt(3);
if(_name != nameList[pos]) {
_name = nameList[pos];
notifyListeners();
}
}
}
在第 6 行代碼中,使用了一個 Dart 的 with 關(guān)鍵詞,,這個用法是表示 NameModel 可以直接調(diào)用 ChangeNotifier 的方法,,比如第 15 行的代碼就是調(diào)用了 ChangeNotifier 類中的方法。上面代碼中,,在 changeName 中設(shè)置完狀態(tài)屬性 _name 以后,,通過 ChangeNotifier 通知監(jiān)聽方。為了性能優(yōu)化,,在第 18 到第 21 行進(jìn)行了判斷,,避免屬性未改變而觸發(fā) build 操作,。接下來看一下,在 name_game 中是如何監(jiān)聽數(shù)據(jù)變化,,代碼實現(xiàn)如下:
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:two_you/model/name_model.dart';
import 'package:two_you/widgets/name_game/random_name.dart';
import 'package:two_you/widgets/name_game/test_other.dart';
import 'package:two_you/widgets/name_game/welcome.dart';
/// 測試隨機(jī)名字游戲組件
class NameGame extends StatelessWidget {
/// 設(shè)置狀態(tài) name
final name = NameModel();
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Provider<String>.value(
child: ChangeNotifierProvider.value(
value: name,
child: Column(
children: <Widget>[
Welcome(),
RandomName(),
],
),
),
),
TestOther(),
],
);
}
}
上述代碼中,,第 13 行獲取狀態(tài)屬性 name ,在 build 邏輯中使用 Provider.value 來封裝需要共享的組件,,String 為 name 相應(yīng)的字段類型,。并且使用 ChangeNotifierProvider 來接受監(jiān)聽數(shù)據(jù)變化,當(dāng)數(shù)據(jù)發(fā)生變化時則觸發(fā)子組件的 build ,。
最后我們再來看其中的一個子組件 RandomName ,,在 RandomName 中展示 name 字段,并且有一個按鈕觸發(fā) changeName 操作,,代碼實現(xiàn)如下,。
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:two_you/model/name_model.dart';
/// 隨機(jī)展示人名
class RandomName extends StatelessWidget {
/// 有狀態(tài)類返回組件信息
@override
Widget build(BuildContext context) {
final _name = Provider.of<NameModel>(context);
print('random name build');
return FlatButton(
child: Text(_name.value),
onPressed: () => _name.changeName(),
);
}
}
第 11 行通過 Provider.of(context) 方式,獲得根節(jié)點(diǎn) NameModel 的句柄,,然后通過 NameModel 的 value 獲得狀態(tài) name 的值,,其次使用 _name.changeName 執(zhí)行 NameModel 的方法,觸發(fā) name 狀態(tài)值的修改,,從而再通過 ChangeNotifier 通知到兩個組件 welcome 和 random_name ,。
以上就完成了整個 Provider 的實現(xiàn)邏輯,相對其他兩種技術(shù)方案,,則更簡潔一些,。
三者的對比
上面三種技術(shù)方案,在同頁面組件共享都沒有任何問題,,在性能方面也都解決了組件更新避免全局子組件的更新問題,。但是 InheritedWidget 在多頁面間數(shù)據(jù)共享比較麻煩(因為需要一個共同的父節(jié)點(diǎn),對于多個頁面來說沒有共同的父節(jié)點(diǎn)這個概念),,這點(diǎn)對于 Redux 和 Provider 則較為簡單。其次由于 Redux 容易陷入無限的深度嵌套,,因此也不建議使用,。所以本專欄推薦使用 Provider 技術(shù)方案,使用方式較為簡單,,其次也不會帶來其他負(fù)面的影響,。
本課時一開始就介紹了關(guān)于多頁面內(nèi)容共享引起的問題,從而思考狀態(tài)管理的技術(shù)方案,,那么通過技術(shù)對比,我們選擇了 Provider ,,接下來我使用 Provider 來完善上一課時中的例子,。
創(chuàng)建 like_num_model
復(fù)制代碼 import 'package:flutter/material.dart';
/// name狀態(tài)管理模塊
class LikeNumModel with ChangeNotifier {
/// 聲明私有變量
int _likeNum = 0;
/// 設(shè)置get方法
int get value => _likeNum;
/// 修改當(dāng)前name,,隨機(jī)選取一個
void like() {
_likeNum++;
notifyListeners();
}
}
由于每次都會自增,因此在 like 函數(shù)中無須判斷是否 likeNum 狀態(tài)有變化,,只要自增了 likeNum 狀態(tài)后通知監(jiān)聽方即可。
main 函數(shù)創(chuàng)建監(jiān)聽組件
由于涉及兩個頁面,并不是兩個組件,,因此這里需要將狀態(tài)提升到 main 函數(shù)中,,mian 組件的實現(xiàn)如下:
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:two_you/model/like_num_model.dart';
import 'package:two_you/pages/home_page.dart';
/// APP 核心入口文件
void main() {
runApp(MyApp());
}
/// MyApp 核心入口界面
class MyApp extends StatelessWidget {
/// 創(chuàng)建 like model
final likeNumModel = LikeNumModel();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Provider<int>.value(
child: ChangeNotifierProvider.value(
value: likeNumModel,
child: MaterialApp(
title: 'Two You', // APP 名字
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue, // APP 主題
),
home: Scaffold(
appBar: AppBar(
title: Text('Two You'), // 頁面名字
),
body: Center(
child: HomePage(),
))),
),
);
}
}
上述代碼第 16 行,,創(chuàng)建了狀態(tài)管理類的對象,并通過 Provider.value 和 ChangeNotifierProvider.value 來封裝組件 HomePage ,由于 ArticlePage 也是在頁面組件中的 MaterialApp 組件下,,因此都可以通過 context 獲取 likeNumModel 句柄,。
使用 likeNumModel
使用 Provider 的好處就在于,不使用的部分完全不需要修改,只需要在使用該狀態(tài)的地方修改即可,。由于 likeNumModel 只在 article_detail_like 和 article_like_bar 中使用,因此修改這兩個組件即可。
article_like_bar 代碼如下:
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:two_you/model/like_num_model.dart';
import 'package:two_you/styles/text_syles.dart';
/// 帖子文章的贊組件
///
/// 包括點(diǎn)贊組件 icon ,,以及組件點(diǎn)擊效果
/// 需要外部參數(shù)[likeNum],點(diǎn)贊數(shù)量
class ArticleLikeBar extends StatelessWidget {
/// 有狀態(tài)類返回組件信息
@override
Widget build(BuildContext context) {
final likeNumModel = Provider.of<LikeNumModel>(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Row(
children: <Widget>[
Icon(Icons.thumb_up, color: Colors.grey, size: 18),
Padding(padding: EdgeInsets.only(left: 10)),
Text(
'${likeModel.value}',
style: TextStyles.commonStyle(),
),
],
),
onPressed: () => likeNumModel.like(),
),
],
);
}
}
在第 15 行獲取操作句柄,,然后在第 26 行獲取屬性 likeNum ,, 在第 31 行執(zhí)行 likeNumModel 執(zhí)行 like 操作,。
article_detail_like 代碼如下:
復(fù)制代碼 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:two_you/model/like_num_model.dart';
import 'package:two_you/styles/text_syles.dart';
/// 帖子詳情頁的贊組件
///
/// 包括點(diǎn)贊組件 icon ,以及組件點(diǎn)擊效果
/// 需要外部參數(shù)[likeNum],點(diǎn)贊數(shù)量
class ArticleDetailLike extends StatelessWidget {
/// 有狀態(tài)類返回組件信息
@override
Widget build(BuildContext context) {
final likeNumModel = Provider.of<LikeNumModel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Icon(Icons.thumb_up, color: Colors.grey, size: 40),
onPressed: () => likeNumModel.like(),
),
Text(
'${likeNumModel.value}',
style: TextStyles.commonStyle(),
),
],
);
}
}
同樣上面的第 15 行獲取 likeNumModel 操作句柄,,然后在第 22 行執(zhí)行 like 操作,在第 25 行顯示點(diǎn)贊數(shù)量,。
接下來我們運(yùn)行下項目,,可以看到效果如圖 5 所示。
圖 5 多頁面狀態(tài)點(diǎn)贊同步效果
總結(jié)
以上就是本課時的所有內(nèi)容,,學(xué)完本課時你需要掌握使用狀態(tài)管理的場景,,常見的狀態(tài)管理有哪些,。本課時的核心是需要你掌握 Provider 的狀態(tài)管理技術(shù)方案,。
至此,,我已經(jīng)將組件的設(shè)計基本介紹完畢,,接下來我將介紹組件的單元測試,,以及完善組件功能,。如果你有疑問,,可以在下方留言,。
點(diǎn)擊此鏈接查看本課時源碼
|