GetX状态管理原理分析
GetX, pub.dev 评分 11535,目前已经超越了provider,成为了flutter中最火的状态管理框架.GetX现在包含的内容很多,这篇主要分析一下其中状态管理的原理.
首先GetX的依赖注入是getx状态管理的关键,这一步保证了数据的全局访问能力,不受制于节点(对比InherentedWidget),只要被注入,全局可访问,通过getx源码可以看到
S put<S>(
S dependency, {
String? tag,
bool permanent = false,
@deprecated InstanceBuilderCallback<S>? builder,
}) {
_insert(
isSingleton: true,
name: tag,
permanent: permanent,
builder: builder ?? (() => dependency));
return find<S>(tag: tag);
}
void _insert<S>({
bool? isSingleton,
String? name,
bool permanent = false,
required InstanceBuilderCallback<S> builder,
bool fenix = false,
}) {
// 这一步使用了Type+tag作为key
final key = _getKey(S, name);
// 这一步就是将传入的GetController保存起来
if (_singl.containsKey(key)) {
final dep = _singl[key];
if (dep != null && dep.isDirty) {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
lateRemove: dep as _InstanceBuilderFactory<S>,
);
}
} else {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
);
}
}
这里GetX使用了一个单例
class GetInstance {
factory GetInstance() => _getInstance ??= const GetInstance._();
const GetInstance._();
static GetInstance? _getInstance;
static final Map<String, _InstanceBuilderFactory> _singl = {};
因此不管是在哪里,都可以获取到这个_singl,也就实现了全局访问的功能
接下来就是如何使用这个GetController,在UI显示上,GetX提供了两种使用方案:
- 使用GetBuilder,手动更新状态
- 使用Obx自动刷新
GetBuilder是一个StatefulWidget
class GetBuilder<T extends GetxController> extends StatefulWidget {
在他的State 的initState中,通过指定的泛型(Type)以及tag获取到对应的GetController(之前被注入的,或者是刚刚创建的),赋值给State中的controller属性,然后调用_subscribeToController,去将当前State的setState方法绑定到GetController上
void initState() {
// _GetBuilderState._currentState = this;
super.initState();
widget.initState?.call(this);
var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
if (widget.global) {
if (isRegistered) {
if (GetInstance().isPrepared<T>(tag: widget.tag)) {
_isCreator = true;
} else {
_isCreator = false;
}
controller = GetInstance().find<T>(tag: widget.tag);
} else {
controller = widget.init;
_isCreator = true;
GetInstance().put<T>(controller!, tag: widget.tag);
}
} else {
controller = widget.init;
_isCreator = true;
controller?.onStart();
}
if (widget.filter != null) {
_filter = widget.filter!(controller!);
}
_subscribeToController();
}
void _subscribeToController() {
_remove?.call();
_remove = (widget.id == null)
? controller?.addListener(
_filter != null ? _filterUpdate : getUpdate,
)
: controller?.addListenerId(
widget.id,
_filter != null ? _filterUpdate : getUpdate,
);
}
void getUpdate() {
if (mounted) setState(() {});
}
在GetController中,_updaters保存了所有使用了当前getController的State刷新回调
Disposer addListener(GetStateUpdate listener) {
assert(_debugAssertNotDisposed());
_updaters!.add(listener);
return () => _updaters!.remove(listener);
}
当需要更新数据的时候,直接在getController中调用update,这个时候就会将_updaters中的所有回调函数都执行一遍,这时候就完成了页面刷新
void update([List<Object>? ids, bool condition = true]) {
if (!condition) {
return;
}
if (ids == null) {
refresh();
} else {
for (final id in ids) {
refreshGroup(id);
}
}
}
void refresh() {
assert(_debugAssertNotDisposed());
_notifyUpdate();
}
void _notifyUpdate() {
for (var element in _updaters!) {
element!();
}
}
Obx自动刷新,响应式状态管理器
这种方式只需要关注于数据,不用关心什么时候刷新的问题,因为在修改完数据后,页面会自动进行刷新
使用Obx,第一步要对数据进行"obs"修饰,本质是将数据包装了一层
final count = 0.obs;
class Rx<T> extends _RxImpl<T> {
Rx(T initial) : super(initial);
@override
dynamic toJson() {
try {
return (value as dynamic)?.toJson();
} on Exception catch (_) {
throw '$T has not method [toJson]';
}
}
}
abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
最终的数据会被包装成RxNotifier,作用后面再说.
接下来该介绍Obx了,我们会发现Obx也是继承自StatefulWidget,所以应该可以想到这种方式最后也是通过调用State中的setState来完成页面刷新的(至少能感觉到)
class Obx extends ObxWidget {
abstract class ObxWidget extends StatefulWidget {
在ObxWidget的State中我们又发现了一个RxNotifier,上面说了数据被包装成了RxNotifier(这里需要注意,目前已经有两个RxNotifier属性了,一个是GetController中数据本身被包装成RxNotifier,一个是State中的属性_observer是一个RxNotifier)
这里我们可以给这两个RxNotifier起个名字,GetController中的叫做dataRxNotifier(D),State中的叫做observerRxNotifier(O),RxNotifier我们可以简单理解他就是一个包装类,里边有一个可以被多次订阅的Stream(subject),
final _observer = RxNotifier();
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
然后看initState,在这里O被监听了,当O发生变化后调用_updateTree,也就是setState
void initState() {
super.initState();
subs = _observer.listen(_updateTree, cancelOnError: false);
}
void _updateTree(_) {
if (mounted) {
setState(() {});
}
}
到这里了,我们可以思考下,如果想要页面刷新,是不是只需要触发_observer(O)的刷新机制就可以了!!!
然后看State中的build方法
Widget build(BuildContext context) =>
RxInterface.notifyChildren(_observer, widget.build);
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;
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;
}
builder就是Obx中传入的builder, observer就是State中的observer,上文我们给它起名字叫做O,这里会发现一个有趣的问题,就是在执行builder之前,我们替换了RxInterface.proxy为当前State中的observer(O),然后执行完后又换了回来,看着一头雾水,其实这里真的是点睛之笔,所有的不可能在这里全部连接了起来,接着往下看,就会豁然开朗.
在我们不熟练的时候,有时候使用Obx,里边没有obs修饰的参数,这个时候getx会报异常,其实就是上边的这段话,所以getx要求,Obx中必须要包含obs修饰的数据
接下来我们就要看builder了,联想一下上边为什么要临时替换RxInterface.proxy
builder中,我们一定会用到的方法是D的value 的getter方法,例如:
Obx(() {
return Row(
children: [
Text(Get
.find<User>()
.name
.value),
],
);
})
我们调用了user的name,然后调用name的value getter(此时的name是一个RxNotifier)
class User extends GetxController {
final name = "小铭".obs;
}
根据经验我们应该能想到,应该是在这里做了什么,才能保证数据修改,可以刷新我么当前的页面,所以我们进入value getter
T get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
在这里不光是将数据返回,还做了一件事RxInterface.proxy?.addListener(subject);
RxInterface.proxy 被使用了,这个时候我们是在D中,完全脱离了O,所以RxInterface.proxy这个属性的目的就是在D中可以使用O,通过每次临时替换,保证了D与O的对应关系的准确性,完美,通过一个静态变量解决了这种通信问题
我们看看addListener这个里边做了什么
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);
}
}
rxGetx就是我们上文中D的Stream,而这里的subject,就是O的Stream
所以上边的代码可以这样写
void addListener(GetStream<T> dStream) {
if (!_subscriptions.containsKey(dStream)) {
final subs = dStream.listen((data) {
if (!oStream.isClosed) oStream.add(data);
});
final listSubscriptions =
_subscriptions[rxGetx] ??= <StreamSubscription>[];
listSubscriptions.add(subs);
}
}
数据改变后,会调用dStream.listen中的回调函数,回调函数又执行了oStream.add(类比sink.add),又会执行oStream.listen中的回调,而 oStream.listen中的回调 正是_updateTree,也就是setState,这里思路一下子就清晰了
所以可以这样理解,GetX响应式状态管理器,其实依赖于两个Stream,一个是数据的Stream,一个是State 的Stream,数据改变时通过监听回调告诉State中的Stream,State中的Stream改变,才实现了页面的刷新.
但是其实这里的GetStream并不是真正的Stream,它实际上是将所有的回调保存在_onData中
List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];
当执行add方法的时候遍历执行_onData中的回调函数,
void add(T event) {
assert(!isClosed, 'You cannot add event to closed Stream');
_value = event;
_notifyData(event);
}
void _notifyData(T data) {
_isBusy = true;
for (final item in _onData!) {
if (!item.isPaused) {
item._data?.call(data);
}
}
_isBusy = false;
}
这里如果可以,我觉得直接建立value和setState的关系更加省事,说实在这两个GetStream真的有点画蛇添足了