楔子
最近寫Flutter項目的時候接觸了getx框架肥印,深深被這個它所吸引识椰,功能強大,api簡潔深碱,狀態(tài)管理機制也堪稱優(yōu)秀腹鹉,本篇就簡單分析一下getx是如果通過Obx和obs就能實現(xiàn)狀態(tài)管理的。
文有點長莹痢,你忍一下种蘸!
計數(shù)器Demo
首先,我們還是以一個計數(shù)器的demo作為引子竞膳,使用getx實現(xiàn)計數(shù)器功能如下:
void main() {
runApp(MaterialApp(
home: CounterDemoPage(),
));
}
class CounterDemoPage extends StatelessWidget {
var counter = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("計數(shù)器Demo"),
),
body: Center(
child: Obx(() => Text("計數(shù)結(jié)果 = ${counter.value}")),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.value += 1;
},
child: Icon(Icons.add),
),
);
}
}
在getx框架的支持下航瞭,CounterDemoPage不再需要繼承StatefulWidget和使用setState()方法而是通過以上代碼就能完成和官方提供Demo一樣的功能,不得不讓人贊嘆一句:有點東西坦辟!
那getx是如何通過如此簡單的操作就完成了狀態(tài)管理刊侯,接下來我們深入的剖析一下。
拓展函數(shù)obs
首先跟蹤代碼obs锉走,發(fā)現(xiàn)這是一個對int類型的拓展函數(shù)滨彻,在調(diào)用obs時會返回一個RxInt的對象(雖然是Rx開頭的藕届,但并不是RxDart框架)。同時getx對其他的一些基本數(shù)據(jù)類型和對象類型都有所拓展亭饵,具體請查看getx的官方文檔休偶。
extension IntExtension on int {
RxInt get obs => RxInt(this);
}
class RxInt extends Rx<int> {
RxInt(int initial) : super(initial);
/// Addition operator.
RxInt operator +(int other) {
value = value + other;
return this;
}
/// Subtraction operator.
RxInt operator -(int other) {
value = value - other;
return this;
}
}
RxInt類里面除了重寫了+和-的運算符外,好像并沒有什么值得關(guān)注的了辜羊,那就繼續(xù)追蹤父類踏兜。
class Rx<T> extends _RxImpl<T> {
Rx(T initial) : super(initial);
@override
dynamic toJson() {
/// 略去無關(guān)代碼
}
}
Rx 里面只實現(xiàn)了一個toJson的方法,繼續(xù)網(wǎng)上追溯_RxImpl:
abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
_RxImpl(T initial) {
_value = initial;
}
void addError(Object error, [StackTrace? stackTrace]) {
///略...
}
Stream<R> map<R>(R mapper(T? data)) => stream.map(mapper);
void update(void fn(T? val)) {
///略...
}
void trigger(T v) {
///略...
}
}
_RxImpl中實現(xiàn)了四個方法八秃,addError是發(fā)送錯誤通知碱妆,update和trigger是更新數(shù)據(jù)相關(guān)方法,map是對Stream類中map方法的封裝昔驱,貌似都不是我們要找的疹尾。不過該類繼承了RxNotifier(重要)并混入了RxObjectMixin,由于RxNotifier過于重要骤肛,我們先看混入的RxObjectMixin:
mixin RxObjectMixin<T> on NotifyManager<T> {
late T _value;
/// 刷新數(shù)據(jù)
void refresh() {
subject.add(value);
}
///調(diào)用set value并返回get value值
T call([T? v]) {
if (v != null) {
value = v;
}
return value;
}
/// 是否第一次創(chuàng)建
bool firstRebuild = true;
///...略去無關(guān)代碼
/// 更新數(shù)據(jù)
set value(T val) {
/// 略...
}
/// 獲取_value的值
T get value {
/// #預(yù)留問題1 這個地方是個重點纳本!這個地方是個重點!萌衬!這個地方是個重點R肌!秕豫!重要的事情說三遍
RxInterface.proxy?.addListener(subject);
return _value;
}
/// 通過subject拿到strema
Stream<T?> get stream => subject.stream;
///下面兩行為getx類庫注釋的翻譯朴艰,我并沒有找到這個方法調(diào)用的位置,應(yīng)該是留給開發(fā)者調(diào)用的混移。
/// 將現(xiàn)有的Stream<T>綁定到此 Rx<T> 以保持值同步祠墅。 (注釋翻譯)
/// 您可以綁定多個源來更新值。 當(dāng)觀察者小部件( GetX或Obx )從小部件樹中卸載時歌径,將自動關(guān)閉訂閱(注釋翻譯)
void bindStream(Stream<T> stream) {
final listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
}
可以看出RxObjectMixin中基本上都是關(guān)于更新數(shù)據(jù)的方法毁嗦,但是數(shù)據(jù)更新后怎么通知到視圖,這里也并沒有提現(xiàn)出來回铛,但是這個類實現(xiàn)了NotifyManager類狗准,我們先不看NotifyManager而是回到_RxImpl類,看它繼承的父類茵肃。
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
_RxImpl的父類RxNotifier繼承自RxInterface并和RxObjectMixin一樣混入和NotifyManager腔长。
先來看一下RxInterface:
abstract class RxInterface<T> {
///看名字也知道,是個判斷是否可以更新的方法
bool get canUpdate;
void addListener(GetStream<T> rxGetx);
void close();
static RxInterface? proxy;
StreamSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError});
/// Avoids an unsafe usage of the `proxy`
static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
/// #預(yù)留問題2 下面會介紹到該方法
}
}
RxInterface中有四個未實現(xiàn)的方法验残,分別是get方法canUpdate捞附、addListener、close和listen,但是都沒有具體實現(xiàn)鸟召。而靜態(tài)方法notifyChildren在前面的追蹤中也沒有發(fā)現(xiàn)在哪個地方有調(diào)用胆绊,所以暫時不用管它,靜態(tài)變量proxy倒是在上面標(biāo)記重點的地方出現(xiàn)了欧募,不過既然是重點压状,要肯定要留到最后再說。
RxNotifier繼承了RxInterface同時也繼承了四個為實現(xiàn)的方法槽片,這四個方法在混入的NotifyManager中得到了實現(xiàn):
mixin NotifyManager<T> {
GetStream<T> subject = GetStream<T>();
final _subscriptions = <GetStream, List<StreamSubscription>>{};
///根據(jù)_subscriptions是否為空判斷是否可以更新
bool get canUpdate => _subscriptions.isNotEmpty;
/// #預(yù)留問題3 這個放到后面介紹
void addListener(GetStream<T> rxGetx) {
if (!_subscriptions.containsKey(rxGetx)) {
final subs = rxGetx.listen((data) {
if (!subject.isClosed) subject.add(data);
});
final listSubscriptions =
_subscriptions[rxGetx] ??= <StreamSubscription>[];
listSubscriptions.add(subs);
}
}
/// #預(yù)留問題4 看起來像是監(jiān)聽數(shù)據(jù)的更新何缓,但是在前面的分析中肢础,并沒有發(fā)現(xiàn)調(diào)用的地方还栓,那我們就先放著吧
StreamSubscription<T> listen(
void Function(T) onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) =>
subject.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError ?? false,
);
/// 對外提供close方法清理內(nèi)存
void close() {
/// 略...
}
}
NotifyManager是obs繼承鏈條可以追蹤到最終的類了,它里面持有了一個GetStream類型的屬性subject传轰,GetStream也是getx框架狀態(tài)管理的核心類剩盒,subject在addListener和listen中都有用到,不過我們暫時先緩一緩慨蛙,看一下Obx的實現(xiàn)辽聊。
自定義Widget Obx
分析完obs的實現(xiàn),然我們看一下自定義Widget Obx是如何實現(xiàn)的:
typedef WidgetCallback = Widget Function();
class Obx extends ObxWidget {
final WidgetCallback builder;
const Obx(this.builder);
@override
Widget build() => builder();
}
Obx類繼承了ObxWidget期贫,但類的功能非常簡單跟匆,只是在構(gòu)造函數(shù)中聲明了一個必傳的參數(shù),而參數(shù)的類型則是一個返回值為Widget的無參函數(shù)通砍,然后在重寫父類的build方法中直接調(diào)用該函數(shù)玛臂。
看來核心的東西應(yīng)該都在Obx的父類ObxWidget中了,我們繼續(xù)深入封孙。
abstract class ObxWidget extends StatefulWidget {
//... 略去無用代碼
@override
_ObxState createState() => _ObxState();
///這個build方法時getx框架定義的迹冤,而非系統(tǒng)方法
@protected
Widget build();
}
ObxWidget是一個繼承自StatefulWidget的Widget,總所周知虎忌,StatefulWidget是需要有一個對應(yīng)的State來管理Widget泡徙,然后查看_ObxState的具體實現(xiàn)。
class _ObxState extends State<ObxWidget> {
#2 /// 又見到了這個類
final _observer = RxNotifier();
#1
late StreamSubscription subs;
#3
@override
void initState() {
super.initState();
///這里調(diào)用了RxNotifier的listen方法膜蠢,也就是NotifyManager的listen方法堪藐,這是 #預(yù)留問題4 中方法調(diào)用的地方
subs = _observer.listen(_updateTree, cancelOnError: false);
}
#4
void _updateTree(_) {
if (mounted) {
setState(() {});
}
}
#5
@override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
#6
@override
Widget build(BuildContext context) =>
RxInterface.notifyChildren(_observer, widget.build);
}
在這里可以看到_ObxState持有兩個屬性。
#1 StreamSubscription是Dart的Steam中提供的類想了解Steam的請查閱Dart | 什么是Stream挑围,定義了訂閱事件的各種方法以及取消方法礁竞。在當(dāng)前類中,StreamSubscription的作用就是在#5 dispose生命周期中取消訂閱贪惹,防止內(nèi)存泄漏的發(fā)生苏章。
#2 我們在追蹤obs的實現(xiàn)的時候,標(biāo)記了一個非常重要的類RxNotifier,在這里又再次遇見了它枫绅,我們創(chuàng)建的每一個Obx對象內(nèi)部都持有一個RxNotifier的實現(xiàn)泉孩。
在_ObxState的#3 initState生命周期中,_observer調(diào)用listen方法開始監(jiān)聽數(shù)據(jù)并淋,并把_updateTree()函數(shù)作為參數(shù)傳了過去寓搬,當(dāng)數(shù)據(jù)更新時,#3 _updateTree()會被調(diào)用县耽,而它又調(diào)用了setState方法句喷,當(dāng)setState方法被調(diào)用的時候,#6 build方法就會被調(diào)用兔毙,重新返回一個數(shù)據(jù)更新后的Widget唾琼,從而實現(xiàn)了界面的刷新。
我們繼續(xù)追蹤#3中listen方法澎剥,最后在get_stream.dart類中锡溯,每個listen方法都會創(chuàng)建一個LightSubscription對象,緩存到了屬性List<LightSubscription<T>>? _onData中哑姚,等待著被臨幸祭饭,而而LightSubscription正是#1 StreamSubscription的子類,被_ObxState中的subs接收叙量。
mixin NotifyManager<T> {
GetStream<T> subject = GetStream<T>();
//NotifyManager中的listen方法
StreamSubscription<T> listen(
void Function(T) onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) =>
//直接調(diào)用了GetStream對象中的listen方法
subject.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError ?? false,
);
}
class GetStream<T> {
List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];
// GetStream中的listen方法
LightSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
final subs = LightSubscription<T>(
removeSubscription,
onPause: onPause,
onResume: onResume,
onCancel: onCancel,
)
..onData(onData)
..onError(onError)
..onDone(onDone)
..cancelOnError = cancelOnError;
addSubscription(subs);
onListen?.call();
return subs;
}
// 把LightSubscription添加到List中等待被臨幸
FutureOr<void> addSubscription(LightSubscription<T> subs) async {
if (!_isBusy!) {
return _onData!.add(subs);
} else {
await Future.delayed(Duration.zero);
return _onData!.add(subs);
}
}
}
回到_ObxState類倡蝙,在#6 build方法中,并沒有直接返回widget.build()直接返回新創(chuàng)建的Widget而是調(diào)用了RxInterface的靜態(tài)方法notifyChildren绞佩,這個就是上面的 #預(yù)留問題2 寺鸥,我們繼續(xù)分析一下這個方法:
static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
final _observer = RxInterface.proxy;
RxInterface.proxy = observer;
final result = builder();
if (!observer.canUpdate) {
RxInterface.proxy = _observer;
/// 這個就是使用Obx中并沒有用到obs修飾的變量,而拋出的異常征炼,用過getx的都知道這個異常??
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
RxInterface.proxy = _observer;
return result;
}
這個方法調(diào)用時做了一個校驗析既,如果使用如下方式使用Obx,則會拋出異常谆奥。
child:Obx(() => Text("計數(shù)結(jié)果"))
///這時就會拋出異常眼坏,因為Obx中沒有可觀察的對象。
這么做的初衷應(yīng)該是想要規(guī)避那些不正確的寫法導(dǎo)致增加內(nèi)存酸些,因為Obx是繼承自StatefulWidget的宰译,而上面的寫法完全可以用StatelessWidget實現(xiàn)。
通過上面的一頓操作魄懂,我們已經(jīng)知道此時我們創(chuàng)建Obx對象中已經(jīng)有一個訂閱者StreamSubscription在等待通知沿侈,那通知什么時候會來呢?我們繼續(xù)分析一下個步驟市栗。
obs和Obx的綁定
經(jīng)過上面的分析缀拭,我們已經(jīng)大致知道了obs和Obx的實現(xiàn)邏輯咳短,那obs的數(shù)據(jù)是如何最終綁定到Obx里呢?我們先回顧一下Demo里面你的這段代碼:
body: Center(
child: Obx(() => Text("計數(shù)結(jié)果 = ${counter.value}")),
)
接下來跟蹤counter.value方法:
T get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
赫然發(fā)現(xiàn)蛛淋,這個方法就是我們上面的#預(yù)留問題1咙好,這個地方有兩個問題:
① RxInterface.proxy對象是什么?
② 參數(shù)subject是什么褐荷?
問題②比較好解答勾效,細(xì)心的同學(xué)應(yīng)該還記得我們創(chuàng)建的RxInt對象是繼承自NotifyManager的,而NotifyManager中就有個GetStream類型的屬性subject叛甫。
問題①比較難层宫,因為要從整個CounterDemoPage的加載說起,整個流程如下:
1其监、CounterDemoPage對象創(chuàng)建萌腿,作為屬性的counter隨后完成創(chuàng)建,counter父類為RxNotifier(1)
2棠赛、CounterDemoPage的build的方法被調(diào)用哮奇,Obx對象被創(chuàng)建,Obx中持有一個Function睛约,返回值為Widget
3、Obx的父類ObxWidget和_ObxState被創(chuàng)建哲身,_ObxState持有了對象RxNotifier(2)
4辩涝、_ObxState的build方法被調(diào)用,調(diào)用到RxInterface.notifyChildren方法勘天,該p1為_ObxState持有的對象RxNotifier(2)怔揩,p2為Obx中的Function
5、執(zhí)行notifyChildren方法
/// 當(dāng)前Obx中持有的obs對象
final _subscriptions = <GetStream, List<StreamSubscription>>{};
static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
/// 此時RxInterface.proxy對象為null脯丝,將null值賦給_observer
final _observer = RxInterface.proxy;
/// 把RxNotifier(2)對象復(fù)給RxInterface.proxy商膊,此時proxy短暫不為空
RxInterface.proxy = observer;
/// 調(diào)用Obx中持有的函數(shù)Function,執(zhí)行步驟6
final result = builder();
/// 判斷是否能更新宠进,如果不能拋出異常晕拆,判斷依據(jù)就是_subscriptions是否為空
if (!observer.canUpdate) {
RxInterface.proxy = _observer;
throw ""
}
/// proxy對象重新置空,開始下一個輪回
RxInterface.proxy = _observer;
/// Obx的Function返回值作為該方法的最終返回值
return result;
}
6材蹬、Obx中的函數(shù)體為return Text("計數(shù)結(jié)果 = ${counter.value}")实幕,這便回到了本小節(jié)剛開始的地方,調(diào)用counter.value方法
結(jié)論:問題①中的RxInterface.proxy對象就是我們創(chuàng)建的Obx對象的父類的state _ObxState中持有的RxNotifier(2)
#預(yù)留問題1解決了堤器,我們繼續(xù)調(diào)用addListener方法昆庇,也就是我們上面的#預(yù)留問題3,我們繼續(xù)跟進(jìn)問題:
/// 當(dāng)前Obx中持有的obs對象
final _subscriptions = <GetStream, List<StreamSubscription>>{};
/// 該方法就是把RxInt中的subject與Obx中的subject做關(guān)聯(lián)
/// 參數(shù)rxGetx為RxInt中的subject
void addListener(GetStream<T> rxGetx) {
/// 判斷當(dāng)前RxInt是否已經(jīng)添加過了闸溃,避免重復(fù)添加
if (!_subscriptions.containsKey(rxGetx)) {
/// 在這個時候整吆,RxInt才開始監(jiān)聽value數(shù)據(jù)的變化
final subs = rxGetx.listen((data) {
/// RxInt中數(shù)據(jù)變化的通知直接轉(zhuǎn)發(fā)到Obx中的GetStream中去
if (!subject.isClosed) subject.add(data);
});
/// 將RxInt中l(wèi)isten方法的返回值StreamSubscription類型的subs添加到_subscriptions中拱撵,作為canUpdate方法的判斷依據(jù)
final listSubscriptions = _subscriptions[rxGetx] ??= <StreamSubscription>[];
listSubscriptions.add(subs);
}
}
看到這里,終于明白RxInt中的RxNotifier(1)和Obx中的RxNotifier(2)是如何做到聯(lián)動通知的了表蝙,Obx的被通知數(shù)據(jù)有更新后裕膀,觸發(fā)了RxNotifier(2)中持有的_updateTree函數(shù),然后以setState的方式重走了_ObxState的build方法勇哗,最終完成了UI的更新昼扛。
在我們點擊按鈕時,觸發(fā)RxInt的value方法欲诺,也是執(zhí)行相同的邏輯抄谐。
從上面的分析可的出,如果一個obs創(chuàng)建的Rx對象扰法,沒有在任何Obx中使用到蛹含,那么該對象不會添加監(jiān)聽方法,節(jié)約了內(nèi)存開銷塞颁,如果Obx沒有使用任何Rx對象浦箱,則會在開發(fā)階段拋出異常,提醒開發(fā)者不規(guī)范的操作祠锣。在RxNotifier(1)和RxNotifier(2)的引用方式也保證了消息不會被錯誤的通知到酷窥,不得不說這是一個很有意思的設(shè)計。
結(jié)案陳詞
最后用簡單的方式總結(jié)一下狀態(tài)管理機制的實現(xiàn)邏輯:
1伴网、通過obs的方式創(chuàng)建RxInt類型的待觀察數(shù)據(jù)counter蓬推,創(chuàng)建了stream對象但沒有被訂閱。
2澡腾、創(chuàng)建Obx并使用了counter沸伏,在Obx的build第一次被調(diào)用時,Obx中的stream與counter中stream設(shè)置了訂閱事件动分,并把counter中觀察到的變化同步給Obx毅糟。
3、Obx中stream觀察到變化澜公,調(diào)用Obx中傳入的函數(shù)參數(shù)姆另,完成UI的刷新。
4玛瘸、以上蜕青。
寫在最后
原創(chuàng)不易,您的關(guān)注和點贊是我最大的動力~