Flutter 有眾多的狀態(tài)管理方案,我除了對(duì) Provider 有一些了解外廓推,其它的一概不熟悉,主要還是因?yàn)榻佑| Flutter 的時(shí)間太短。
我目前一直是用的原生的 setState艇炎,它已經(jīng)能很好的滿足我的需要了。并且它足夠簡(jiǎn)單腾窝。我目前基于自己的業(yè)務(wù)場(chǎng)景似乎找不到任何去選用其它狀態(tài)管理庫(kù)的理由缀踪。
我一直認(rèn)為對(duì)于一個(gè)庫(kù)來(lái)講,解決問(wèn)題并保持簡(jiǎn)單好用是最重要的虹脯。如果背離了這個(gè)點(diǎn)驴娃,就很有可能導(dǎo)致過(guò)度設(shè)計(jì)。我見(jiàn)過(guò)一些人把我認(rèn)為的最差實(shí)踐當(dāng)成了最佳實(shí)踐循集,而他們卻渾然不知托慨。
當(dāng)然使用 setState 時(shí),也不是完全的裸用暇榴。我做了相當(dāng)輕量級(jí)的封裝厚棵,只有 110 行代碼。我把它稱為 PVState 架構(gòu)模式蔼紧。它既是一種和傳統(tǒng) MVC婆硬、MVP 平輩的架構(gòu)模式,也是一個(gè)輕量級(jí)的狀態(tài)管理方案奸例。我們來(lái)一探究竟吧彬犯。
PVState 概念介紹
首先我封裝了一個(gè) BaseState,所有的 State 都直接或間接地繼承了它查吊。它的子類分為兩種谐区,P State 和 V State。P State 的全稱是 Presenter State逻卖,它負(fù)責(zé)業(yè)務(wù)邏輯宋列。V State 的全稱是 View State,它負(fù)責(zé) UI评也,主要是重寫(xiě) build 方法建立數(shù)據(jù)和 Widget 的單向綁定關(guān)系炼杖。
它們的關(guān)系如圖所示:
這里有一個(gè)很重要的點(diǎn)是 V State 繼承了 P State。
原則上來(lái)講盗迟,所有與業(yè)務(wù)邏輯相關(guān)的方法坤邪、變量、表達(dá)式均定義在 P State 中罚缕。所有與 UI 相關(guān)的東西比如約束布局的 id艇纺、顏色、buildXXX 系列方法均定義在 V State 中。由于兩者的繼承關(guān)系黔衡,V State 可以無(wú)縫地訪問(wèn) P State 中定義的各種方法消约、變量、表達(dá)式员帮。
當(dāng)要更新 UI 時(shí)或粮,只需要在 P State 中調(diào)用 setState 即可。業(yè)務(wù)邏輯和 UI 實(shí)現(xiàn)了完全解耦捞高,同時(shí)保持了簡(jiǎn)單氯材。
由于這個(gè)架構(gòu)模式很簡(jiǎn)單,相信你已經(jīng)聽(tīng)懂了硝岗,接下來(lái)我們來(lái)分析一下我封裝的源碼氢哮。
PVState 源碼分析
BaseState 的代碼如下:
abstract class BaseState<T extends StatefulWidget> extends State<T> {
static List<BasePagePStateMixin> pageStack = [];
@override
void initState() {
super.initState();
if (this is BasePagePStateMixin) {
if (pageStack.isNotEmpty) {
(pageStack.last).onPushNext();
}
pageStack.add(this as BasePagePStateMixin);
scheduleMicrotask(() {
(this as BasePagePStateMixin).onPush();
});
}
}
P? find<P extends BasePagePStateMixin>() {
for (final element in pageStack.reversed) {
if (element is P) {
return element;
}
}
return null;
}
@override
void dispose() {
super.dispose();
if (this is BasePagePStateMixin) {
(this as BasePagePStateMixin).onPop();
int index = pageStack.indexOf(this as BasePagePStateMixin);
pageStack.removeAt(index);
if (index == pageStack.length - 1 && pageStack.isNotEmpty) {
scheduleMicrotask(() {
pageStack.last.onPopNext();
});
}
}
}
}
BaseState 目前做了兩件事情,一是使用靜態(tài)變量存儲(chǔ)了所有的頁(yè)面型 State型檀,可以用來(lái)做路由棧監(jiān)聽(tīng)冗尤,起到了 RouteAware 的作用。
什么是頁(yè)面型 State 呢胀溺?這里我把 State 劃分成了三種裂七,分別是頁(yè)面型 State、對(duì)話框型 State 和嵌入型 State仓坞。它們分別對(duì)應(yīng)于整個(gè)頁(yè)面的 StatefulWidget背零、整個(gè)對(duì)話框的 StatefulWidget 和嵌入到頁(yè)面內(nèi)的 StatefulWidget。原則上只有頁(yè)面型 State 才有路由棧監(jiān)聽(tīng)的能力无埃,對(duì)吧徙瓶?
不同型的 State 應(yīng)繼承不同型的 BaseState,源碼如下:
abstract class BasePState<T extends StatefulWidget> extends BaseState<T> {
@override
Widget build(BuildContext context) {
throw Exception('Do not call super.build()');
}
}
/// For pages
abstract class BasePagePState<T extends StatefulWidget> extends BasePState<T>
with BasePagePStateMixin {}
/// For dialogs
abstract class BaseDialogPState<T extends StatefulWidget> extends BasePState<T>
with BaseDialogPStateMixin {}
/// For embedded widgets
abstract class BaseWidgetPState<T extends StatefulWidget> extends BasePState<T>
with BaseWidgetPStateMixin {}
mixin BasePagePStateMixin {
void onPush() {}
void onPop() {}
void onPushNext() {}
void onPopNext() {}
}
mixin BaseDialogPStateMixin {}
mixin BaseWidgetPStateMixin {}
如圖所示:
此外嫉称,BaseState 還提供了 find 方法侦镇,用于在路由棧中向前查找 State,比如查找 AppState织阅,進(jìn)行一些全局的操作壳繁。這里可以使用 ValueNotifier 進(jìn)行多頁(yè)面的狀態(tài)共享,我提供了簡(jiǎn)易方法:
ValueNotifier obs<V>(V? initialValue) {
return ValueNotifier(initialValue);
}
最后蒲稳,我提供了一個(gè)名為 Stateful 的通用 StatefulWidget氮趋,這樣你就無(wú)需再為每一個(gè) State 定義一個(gè) StatefulWidget 了伍派。源碼如下:
class Stateful<T extends BasePState> extends StatefulWidget {
final T state;
final Map<String, dynamic>? arguments;
static Stateful of<S extends BasePState>(
S newState, {
Key? key,
Map<String, dynamic>? arguments,
}) {
return Stateful(
key: key,
state: newState,
arguments: arguments,
);
}
const Stateful({
Key? key,
required this.state,
this.arguments,
}) : super(key: key);
@override
State createState() {
return state;
}
}
使用方法如下:
void main() {
runApp(Stateful.of(CounterVState()));
}
它還支持傳參江耀,用法如下:
void main() {
runApp(Stateful.of(CounterVState(), arguments: {
'initialCount': 0,
}));
}
abstract class CounterPState extends BasePagePState<Stateful> {
int count = 0;
void add() {
setState(() {
count++;
});
}
@override
void initState() {
super.initState();
count = widget.arguments!['initialCount'];
}
}
可以直接在 initState 中獲取到參數(shù)。這里切記要為 BasePagePState 加上 Stateful 泛型參數(shù)诉植。
計(jì)數(shù)器示例
最后看看用 PVState 架構(gòu)模式實(shí)現(xiàn)的計(jì)數(shù)器的完整源碼吧:
void main() {
runApp(Stateful.of(CounterVState(), arguments: {
'initialCount': 0,
}));
}
abstract class CounterPState extends BasePagePState<Stateful> {
late int count;
void add() {
setState(() {
count++;
});
}
@override
void initState() {
super.initState();
count = widget.arguments!['initialCount'];
}
}
class CounterVState extends CounterPState {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: ConstraintLayout(
children: [
const Text(
'You have pushed the button this many times:',
).applyConstraint(
centerTo: parent,
),
Text(
'$count', // Direct access to count
style: Theme.of(context).textTheme.headline4,
).applyConstraint(
outBottomCenterTo: rId(0),
),
FloatingActionButton(
onPressed: add, // Widget and logic separation
child: const Icon(Icons.add),
).applyConstraint(
bottomRightTo: parent.rightMargin(20).bottomMargin(20),
)
],
),
),
);
}
}
結(jié)束語(yǔ)
在我的實(shí)際使用中祥国,我還為 BaseState 封裝了一些其它的能力,比如 showLoading、showToast舌稀、sendRequest 等等啊犬。你也可以結(jié)合實(shí)際情況添加一些東西。架構(gòu)模式的源碼已開(kāi)源到 https://github.com/hackware1993/Flutter_PVState
我是 hackware壁查,關(guān)注我(公眾號(hào):FlutterFirst)觉至,一起成長(zhǎng)!