GetX也是Flutter中非常火的一个项目,它不是一个单一功能的库,而是一个功能非常全面的脚手架,包括有依赖注入、状态管理、多语言、路由管理、网络请求等等,实际上对于规模较小的应用,基本上只使用这一个库就可以完成整个项目的开发了。当然也可以选择性的使用,例如我们可以只使用它的状态管理功能,其他功能使用别的方式实现也是可以的。
本文专注于GetX的依赖注入和状态管理,版本是4.7.2。
依赖注入 依赖注入是GetX非常重要的一个功能,它的很多其他功能其实都是以依赖注入为基础的,例如状态管理中,对于状态的提供就是通过依赖注入来创建了查询的。
使用中我们其实更倾向于用它作为依赖管理而不是依赖注入,即创建对象依旧由我们来控制,只是我们创建完之后交由GetX进行管理。
注入 注入一个对象,我们最常用的注入方式就是直接通过Get.put添加一个已有的对象:
1 2 3 4 5 S put<S>(S dependency, {String? tag, bool permanent = false , InstanceBuilderCallback<S>? builder}) => GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
虽然该方法参数有四个,但是最后的一个参数builder并没有用到,这个builder是用来构建对象的函数,因为我们是直接注入已有对象的,因此不需要关注如何构建。
最后的关键实际上是直接调用了GetInstance().put完成的注入:
1 2 3 4 5 6 7 8 9 class GetInstance { factory GetInstance() => _getInstance ??= const GetInstance._(); const GetInstance._(); static GetInstance? _getInstance; static final Map <String , _InstanceBuilderFactory> _singl = {}; ... }
简单看下,实际通过GetInstance()拿到的是全局单例对象,它内部有一个map集合,用于存储所有的对象,这就是依赖注入的基础,通过全局单例来实现全局都能进行注入和获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 S put<S>( S dependency, { String? tag, bool permanent = false , @Deprecated ("Do not use builder, it will be removed in the next update" ) InstanceBuilderCallback<S>? builder, }) { _insert( isSingleton: true , name: tag, permanent: permanent, builder: builder ?? (() => dependency)); return find<S>(tag: tag); }
可以看到最终的逻辑是通过_insert完成的,实际上GetX的多种注入方式,如:put、lazyPut、putAsync、create最终都是通过_insert,将各种参数打包成一个_InstanceBuilderFactory,最终存储在集合中的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void _insert<S>({ bool? isSingleton, String? name, bool permanent = false , required InstanceBuilderCallback<S> builder, bool fenix = false , }) { final key = _getKey(S, name); 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, ); } }String _getKey(Type type, String? name) { return name == null ? type.toString() : type.toString() + name; }
这逻辑就很简单了,就是构建key(类型+tag),构建value(_InstanceBuilderFactory),然后将其存储在map集合中就完成了。其中_InstanceBuilderFactory如他的名字一样,实例构建工厂,当查询时,也会构建对应的key,然后查询到这个factory,由它来给出依赖的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class _InstanceBuilderFactory <S > { bool? isSingleton; bool fenix; S? dependency; InstanceBuilderCallback<S> builderFunc; bool permanent = false ; bool isInit = false ; _InstanceBuilderFactory<S>? lateRemove; bool isDirty = false ; String? tag; _InstanceBuilderFactory( this .isSingleton, this .builderFunc, this .permanent, this .isInit, this .fenix, this .tag, { this .lateRemove, }); void _showInitLog() { if (tag == null ) { Get.log('Instance "$S " has been created' ); } else { Get.log('Instance "$S " has been created with tag "$tag "' ); } } S getDependency() { if (isSingleton!) { if (dependency == null ) { _showInitLog(); dependency = builderFunc(); } return dependency!; } else { return builderFunc(); } } }
_InstanceBuilderFactory中定义很多的参数,分别用于不同的场景。例如被标记为单例,则每次获取时实际上都是取的同一个对象,否则每次都是构建一个新的对象。
获取 获取只有一个方法,Get.find。
1 S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
还是走的GetInstance()单例对象的find方法,这也与我们的预期相符,毕竟我们存储时就是将它存在GetInstance()中的一个map集合里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 bool isRegistered<S>({String? tag}) => _singl.containsKey(_getKey(S, tag)); S find<S>({String? tag}) { final key = _getKey(S, tag); if (isRegistered<S>(tag: tag)) { final dep = _singl[key]; final i = _initDependencies<S>(name: tag); return i ?? dep.getDependency() as S; } else { throw ... } }
这里查询实际上也是直接构建key,然后从集合中取出对应的factory,从而获取到实例。注意这里在取出factory之后,还会调用一次_initDependencies,当它返回值为空时,才通过fatory来获取依赖对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 S? _initDependencies<S>({String? name}) { final key = _getKey(S, name); final isInit = _singl[key]!.isInit; S? i; if (!isInit) { i = _startController<S>(tag: name); if (_singl[key]!.isSingleton!) { _singl[key]!.isInit = true ; if (Get.smartManagement != SmartManagement.onlyBuilder) { RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name)); } } } return i; }
在初始化依赖中,会做两件事:一是对controller初始化,一是建立与路由的关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 S _startController<S>({String? tag}) { final key = _getKey(S, tag); final i = _singl[key]!.getDependency() as S; if (i is GetLifeCycleBase) { i.onStart(); if (tag == null ) { Get.log('Instance "$S " has been initialized' ); } else { Get.log('Instance "$S " with tag "$tag " has been initialized' ); } if (!_singl[key]!.isSingleton!) { RouterReportManager.appendRouteByCreate(i); } } return i; }static void appendRouteByCreate(GetLifeCycleBase i) { _routesByCreate[_current] ??= HashSet<Function >(); _routesByCreate[_current]!.add(i.onDelete); }
这一步的初始化路由中,会先获取到依赖对象Controller,然后执行它的onStart方法,如果是非单例对象将其onDelete加入到当前路由的集合中,从而跟随路由的生命周期而执行。单例对象会在销毁时onDelete。
1 2 3 4 5 6 7 8 9 static void reportDependencyLinkedToRoute(String depedencyKey) { if (_current == null ) return ; if (_routesKey.containsKey(_current)) { _routesKey[_current!]!.add(depedencyKey); } else { _routesKey[_current] = <String >[depedencyKey]; } }
获取的整体逻辑也很简单,就是根据key获取到factory,然后开始构建对象。对于标记为单例的依赖,会在初始化时做一些操作。如果是Controller,则会调用它的onStart,并且将其onDelete加入到当前路由的_routesByCreate中。然后就是同时,也会将依赖对象的key加入到当前路由的_routesKey中。这也是依赖管理能够智能的处理依赖对象的生命周期的关键。这也就是说,如果用到了GetX的依赖注入,就需要用它的路由管理,否则注入的对象一直在内存中无法释放。
生命周期 GetX的路由管理,实际上也就是在最顶层通过GetMaterialApp来替代MaterialApp,看起来是GetX实现的,但当我们点到内部逻辑的时候,就发现它其实就是对MaterialApp做了一层包装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class GetMaterialApp extends StatelessWidget { @override Widget build(BuildContext context) => GetBuilder<GetMaterialController>( ... builder: (_) => routerDelegate != null ? MaterialApp.router( ... : MaterialApp( ... navigatorObservers: (navigatorObservers == null ? <NavigatorObserver>[ GetObserver(routingCallback, Get.routing) ] : <NavigatorObserver>[ GetObserver(routingCallback, Get.routing) ] ..addAll(navigatorObservers!)) ), ... }
可以看到,最终做路由的还是MaterialApp,只是GetX对他做了一些封装而已。而关于生命周期部分的,实际就在GetObserver中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class GetObserver extends NavigatorObserver { ... @override void didPop(Route route, Route? previousRoute) { super .didPop(route, previousRoute); ... if (previousRoute != null ) { RouterReportManager.reportCurrentRoute(previousRoute); } ... } @override void didPush(Route route, Route? previousRoute) { super .didPush(route, previousRoute); RouterReportManager.reportCurrentRoute(route); ... } @override void didRemove(Route route, Route? previousRoute) { super .didRemove(route, previousRoute); ... if (route is GetPageRoute) { RouterReportManager.reportRouteWillDispose(route); } } @override void didReplace({Route? newRoute, Route? oldRoute}) { super .didReplace(newRoute: newRoute, oldRoute: oldRoute); ... if (newRoute != null ) { RouterReportManager.reportCurrentRoute(newRoute); } if (oldRoute is GetPageRoute) { RouterReportManager.reportRouteWillDispose(oldRoute); } ... } }
关键的就是这几个路由的回调方法,对应了路由的生命周期。主要就是在路由界面切换时,设置RouterReportManager中的current为新路由,当界面移除时,通过reportRouteWillDispose来通知做回收操作。那么回到RouterReportManager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class RouterReportManager <T > { static final Map <Route?, List <String >> _routesKey = {}; static final Map <Route?, HashSet<Function >> _routesByCreate = {}; static void reportCurrentRoute(Route newRoute) { _current = newRoute; } static void reportRouteWillDispose(Route disposed) { final keysToRemove = <String >[]; _routesKey[disposed]?.forEach(keysToRemove.add); if (_routesByCreate.containsKey(disposed)) { for (final onClose in _routesByCreate[disposed]!) { onClose(); } _routesByCreate[disposed]!.clear(); _routesByCreate.remove(disposed); } for (final element in keysToRemove) { GetInstance().markAsDirty(key: element); } keysToRemove.clear(); } }
它内部有三个静态变量,一个是current指向当前路由,一个是_routesKey内部存储的是每个路由中所使用的依赖对象的key(find)的时候加入的,一个是_routesByCreate存放的是每个路由中的所使用的Controller的onDelete方法。
当界面被移除时,就会通过reportRouteWillDispose方法来触发onDelete和移除依赖。这里并没有直接移除,而只是将其标记为dirty 。
1 2 3 4 5 6 7 8 9 10 11 12 class GetInstance { void markAsDirty<S>({String? tag, String? key}) { final newKey = key ?? _getKey(S, tag); if (_singl.containsKey(newKey)) { final dep = _singl[newKey]; if (dep != null && !dep.permanent) { dep.isDirty = true ; } } } }
真正移除的逻辑实际上是否发生在PageRoute的回调中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class GetPageRoute <T > extends PageRoute <T > with GetPageRouteTransitionMixin <T >, PageRouteReportMixin { ... }mixin PageRouteReportMixin<T> on Route<T> { @override void install() { super .install(); RouterReportManager.reportCurrentRoute(this ); } @override void dispose() { super .dispose(); RouterReportManager.reportRouteDispose(this ); } }
在GetPageRoute中混入了一个PageRouteReportMixin类,然后在其对应的声明周期中做了一些处理,主要是在dispose的时候调用了reportRouteDispose。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class RouterReportManager <T > { static void reportRouteWillDispose(Route disposed) { ... } static void reportRouteDispose(Route disposed) { if (Get.smartManagement != SmartManagement.onlyBuilder) { _removeDependencyByRoute(disposed); } } static void _removeDependencyByRoute(Route routeName) { final keysToRemove = <String >[]; _routesKey[routeName]?.forEach(keysToRemove.add); if (_routesByCreate.containsKey(routeName)) { for (final onClose in _routesByCreate[routeName]!) { onClose(); } _routesByCreate[routeName]!.clear(); _routesByCreate.remove(routeName); } for (final element in keysToRemove) { final value = GetInstance().delete(key: element); if (value) { _routesKey[routeName]?.remove(element); } } keysToRemove.clear(); }
从这里也就可以看出,想要实现依赖的生命周期管理,即当路由移除时,同时销毁该路由上使用的依赖对象,是要依赖GetX的路由管理的,也就是需要有对应的GetObserver和GetPageRoute。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class GetInstance { bool delete<S>({String? tag, String? key, bool force = false }) { final newKey = key ?? _getKey(S, tag); final dep = _singl[newKey]; final _InstanceBuilderFactory builder; if (dep.isDirty) { builder = dep.lateRemove ?? dep; } else { builder = dep; } if (builder.permanent && !force) { return false ; } final i = builder.dependency; if (i is GetxServiceMixin && !force) { return false ; } if (i is GetLifeCycleBase) { i.onDelete(); } if (builder.fenix) { builder.dependency = null ; builder.isInit = false ; return true ; } else { if (dep.lateRemove != null ) { dep.lateRemove = null ; return false ; } else { _singl.remove(newKey); return true ; } } } }
前面说过,如果要使用依赖注入,就需要同时使用它的路由管理,这是为了能让他正常处理生命周期问题。当然了,如果确实是不想使用它的依赖管理,我们也可以自己定义一个NavigatorObserver,然后在这里面手动处理RouterReportManager。
总结下:put时构建_InstanceBuilderFactory,插入到GetInstance()中的map集合中。然后初次使用时,会将已使用的依赖的key加入到与当前路由相关的集合中,然后如果依赖是Controller也会调用它的onStart,然后当路由销毁时,会遍历key然后将其对应的_InstanceBuilderFactory从GetInstance中移除掉,并且会同步调用Controller的onDelete,从而实现了生命周期的管理。
状态管理 状态管理方面也是基本上利用的观察者模式,由顶层提供状态,子组件注册监听,当状态发生变换时刷新子组件以响应状态。当然,这里状态并不是由顶层提供的,而是直接利用了它的依赖注入功能,将持有状态的控制器注入到GetInstance中,这样子组件都能通过GetInstance获取了。并且,界面的控制器还能跟随界面进行变化,当界面销毁时同时会删除对应的控制器,非常方便。
状态管理有两种类型,一种是直接通过GetController,一种是通过obs拓展。
GetxController 提供状态的控制器可以使用GetxController,当然本质上使用任何对象都是可以的,使用GetxController是因为它本身有实现了GetLifeCycleBase,能够响应组件的生命周期变化,从而可以在合适的位置执行合适的操作。
1 2 3 4 5 6 7 8 9 10 class CircleController extends GetxController { Color _color = Colors.red; Color get color => _color; set color(Color value) { _color = value; update(); } }
其中GetxController的实现也是基于Listenable的,也是允许别的组件注册监听,当状态变化后,需要手动触发监听者的回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 abstract class GetxController extends DisposableInterface with ListenableMixin , ListNotifierMixin { void update([List <Object >? ids, bool condition = true ]) { if (!condition) { return ; } if (ids == null ) { refresh(); } else { for (final id in ids) { refreshGroup(id); } } } }
我们通知对应的观察者时,应该直接调用update方法,而不是其他的refresh方法(虽然也是可以的)。从这个方法我们可以看出。GetxController将注册的监听分为了两类,一类是普通的监听者,一类是带id的监听者。
然后看下它的继承类以及混入类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 abstract class DisposableInterface extends GetLifeCycle { @override @mustCallSuper void onInit() { super .onInit(); Get.engine.addPostFrameCallback((_) => onReady()); } @override void onReady() { super .onReady(); } @override void onClose() { super .onClose(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 mixin ListenableMixin implements Listenable {}mixin ListNotifierMixin on ListenableMixin { List <GetStateUpdate?>? _updaters = <GetStateUpdate?>[]; HashMap<Object? , List <GetStateUpdate>>? _updatersGroupIds = HashMap<Object? , List <GetStateUpdate>>(); @protected void refresh() { assert (_debugAssertNotDisposed()); _notifyUpdate(); } void _notifyUpdate() { for (var element in _updaters!) { element!(); } } void _notifyIdUpdate(Object id) { if (_updatersGroupIds!.containsKey(id)) { final listGroup = _updatersGroupIds![id]!; for (var item in listGroup) { item(); } } } @protected void refreshGroup(Object id) { assert (_debugAssertNotDisposed()); _notifyIdUpdate(id); } @protected void notifyChildrens() { TaskManager.instance.notify(_updaters); } @override void removeListener(VoidCallback listener) { assert (_debugAssertNotDisposed()); _updaters!.remove(listener); } void removeListenerId(Object id, VoidCallback listener) { assert (_debugAssertNotDisposed()); if (_updatersGroupIds!.containsKey(id)) { _updatersGroupIds![id]!.remove(listener); } _updaters!.remove(listener); } @mustCallSuper void dispose() { assert (_debugAssertNotDisposed()); _updaters = null ; _updatersGroupIds = null ; } @override Disposer addListener(GetStateUpdate listener) { assert (_debugAssertNotDisposed()); _updaters!.add(listener); return () => _updaters!.remove(listener); } Disposer addListenerId(Object? key, GetStateUpdate listener) { _updatersGroupIds![key] ??= <GetStateUpdate>[]; _updatersGroupIds![key]!.add(listener); return () => _updatersGroupIds![key]!.remove(listener); } void disposeId(Object id) { _updatersGroupIds!.remove(id); } }
就是GetxController将注册的观察者分为了两类,一种是普通的观察者,一种是带id的观察者。这样,当状态发生变化时,可以直接通过update()通知到所有的普通观察者状态发生了变化,也可以通过update([id1, id2])通知指定id的通知者进行刷新。
这对于局部刷新还是比较实用的,例如在controller中有两个状态A和B,然后组件WidgetA使用状态A,组件WidgetB使用状态B,这样这两个组件在注册监听的时候,就可以指定不同的id进行注册,当状态A发生变化时,就可以只通知A对应的id的观察者。
状态提供上就直接继承自GetxController即可,使用上在组件中可以通过GetBuilder来注册监听:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 GetBuilder<CircleController>( builder: (controller) => Container( width: controller.size, height: controller.size, color: controller.color, child: Center( child: ElevatedButton( onPressed: () {}, child: Text('Hello' ), ), ), ), );
使用上也比较简单,直接通过GetBuilder包裹住需要使用状态的组件即可,当状态发生变化时(触发了update方法),就会通知到这里来进行刷新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const GetBuilder({ Key? key, this .init, this .global = true , required this .builder, this .autoRemove = true , this .assignId = false , this .initState, this .filter, this .tag, this .dispose, this .id, this .didChangeDependencies, this .didUpdateWidget, })
构造方法参数较多,必选参数是builder,用于构建组件树的。此外,如果需要感应生命周期的变化,可以传入对应的生命周期的回调,另外filter也是比较重要的。一般情况下,我们会传入builder和filter两个参数。
1 2 3 4 5 class GetBuilder <T extends GetxController > extends StatefulWidget { ... @override GetBuilderState<T> createState() => GetBuilderState<T>(); }
基本上各个状态管理的库原理都是一样的,将依赖状态的组件通过StatefulWidget包裹,然后通过观察者模式观测状态的变化,当状态发生变化时,通过setState触发重建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class GetBuilderState <T extends GetxController > extends State <GetBuilder <T >> with GetStateUpdaterMixin { @override void initState() { 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 _filterUpdate() { var newFilter = widget.filter!(controller!); if (newFilter != _filter) { _filter = newFilter; getUpdate(); } } @override void dispose() { super .dispose(); widget.dispose?.call(this ); if (_isCreator! || widget.assignId) { if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) { GetInstance().delete<T>(tag: widget.tag); } } _remove?.call(); } @override void didChangeDependencies() { super .didChangeDependencies(); widget.didChangeDependencies?.call(this ); } @override void didUpdateWidget(GetBuilder oldWidget) { super .didUpdateWidget(oldWidget as GetBuilder<T>); if (oldWidget.id != widget.id) { _subscribeToController(); } widget.didUpdateWidget?.call(oldWidget, this ); } @override Widget build(BuildContext context) { return widget.builder(controller!); } }
在GetBuilderState中,主要做了生命周期的回调,以及在initState的时候向Controller中注册监听,并且在dispose的时候移除监听。然后就是依赖注入,它会在initState的时候主动帮我们查询到对应的依赖对象,并且在dispose的时候删除依赖对象(只有非单例对象才会删除,因为说明这个对象是在GetBuilder中find时新创建的,所以需要删除掉)。
当传入了filter后,刷新是通过_filterUpdate方法触发的,它会根据filter来计算controller,只有计算后的结果与之前不一致,说明controller中有我们关注的字段发生了变化,此时才会触发刷新。刷新方式是通过getUpdate进行刷新的,我们看下实现:
1 2 3 4 5 mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> { void getUpdate() { if (mounted) setState(() {}); } }
原理还是非常简单,直接通过setState进行刷新。实际上在GetxController中,也可以通过指定id的注册监听器的方式实现局部刷新,这样在状态变化时就能只通知对应的id的观察者即可,这种性能比通过filter实现更高一些,因为它是从源头上控制的,而filter是在已经触发的回调中控制的。
obs、obx 对于需要更加精准的控制状态的场景下,使用obs拓展,将状态本身封装成一个可观察的数据类型。这样子组件就不需要直接对GetxController进行监听,而是可以更加精准的对状态本身进行监听。
1 2 3 4 5 6 7 8 9 10 class CircleController extends GetxController { final txt = 'Hello' .obs; } ElevatedButton( onPressed: () { controller.txt.value = 'newTxt' ; }, child: Obx(() => Text(controller.txt.value)), )
可以看到,使用方式上更加简便了。直接声明的对象通过obs拓展,使用的组件用Obx进行包裹即可,唯一的缺陷可能就是需要我们自己去find对应的控制器了(也可以使用GetView来帮我们获取)。
1 2 3 4 extension RxT<T> on T { Rx<T> get obs => Rx<T>(this ); }
实际上,在前面还有很对RxInt和RxBool等拓展,是针对于基本属性的,使得我们可以不通过.value获取值,而是可以直接使用。这里我们看下对于通用的数据类型的拓展,实际就是将对象包装在了Rx中。
1 2 3 4 5 6 7 8 9 10 11 12 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]' ; } } }
这里没啥看的,就重写了toJson的方法,具体就不在细看了,总之就是在_RxImpl也是实现了一套基于观察者模式的代码,使得可以注册和移除监听。然后就是使用方面,直接通过Obx将用到状态的部分进行包裹即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Obx extends ObxWidget { final WidgetCallback builder; const Obx(this .builder, {Key? key}) : super (key: key); @override Widget build() => builder(); }abstract class ObxWidget extends StatefulWidget { const ObxWidget({Key? key}) : super (key: key); @override ObxState createState() => ObxState(); @protected Widget build(); }class ObxState extends State <ObxWidget > { final _observer = RxNotifier(); late StreamSubscription subs; @override void initState() { super .initState(); subs = _observer.listen(_updateTree, cancelOnError: false ); } void _updateTree(_) { if (mounted) { setState(() {}); } } @override void dispose() { subs.cancel(); _observer.close(); super .dispose(); } @override Widget build(BuildContext context) => RxInterface.notifyChildren(_observer, widget.build); }
Obx本质上就是一个有状态组件StatefulWidget,然后它在内部创建了一个观察者并设置了对应的回调方法,当触发回调时,会通过setState来触发刷新。到这里并没有发现与我们创建的Rx对象有什么关联,因为它的关联是发生在build的时候,RxInterface.notifyChildren:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 abstract class RxInterface <T > { static RxInterface? proxy; static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) { final oldObserver = RxInterface.proxy; RxInterface.proxy = observer; final result = builder(); if (!observer.canUpdate) { RxInterface.proxy = oldObserver; throw ...; } RxInterface.proxy = oldObserver; return result; } }
这里可以看到,在构建的组件的时候,会先通过RxInterface将它的静态属性proxy替换成我们的Obx中创建的_observer,然后执行build进行构建,然后在判断是否能够更新(是否用到了Rx对象),最后再将proxy替换回来。
也就是说实际上到这里我们还是没有将_observer与Rx对象进行关联的,但是在notifyChildren这个方法中的流程:替换proxy->build->检测是否可更新->替换回老的proxy,其实就说明了关联的过程肯定是在build中的,不然不会再执行完build后就去检测是否能更新的。
而build是我们构建Obx的时候传入的用于构建widget的一个函数,本身也没做什么处于,唯一和Rx相关的就是用到了Rx的值,Obx(() => Text(controller.txt.value)),也就是它的value属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 abstract class _RxImpl <T > extends RxNotifier <T > with RxObjectMixin <T > { ... }class RxNotifier <T > = RxInterface <T > with NotifyManager <T >;// 实际用于观察者模式的GetStream 在这里 mixin NotifyManager <T > { GetStream<T> subject = GetStream<T>(); ... }mixin RxObjectMixin<T> on NotifyManager<T> { late T _value; T get value { RxInterface.proxy?.addListener(subject); return _value; } ... }
也就是当我们构建Obx组件的时候,会先临时将RxInterface的静态属性proxy替换成Obx内部的_observer,然后在构建时会用到Rx.value,而在get value时会将当前的Rx对象的subject与静态的proxy进行关联,从而实现了它们之间的监听关系。
因此在构建完成之后,就可以检测是否能够更新(是否与Rx建立了联系),不能更新直接抛异常,也就是说Obx组件必须要在内部用到Rx属性。
总结:绕的比较多,不过整体来说两种方式的原理都是一样的。首先将使用状态的组件用StatelessWidget包一层,然后与状态进行关联,当状态发生变化时,直接setState触发局部组件的刷新。
总结 Getx使用它的依赖注入功能提供状态,子组件通过StatelessWidget包裹住使用状态的组件,然后向状态注册监听,当监听到变化后使用setState触发刷新。(Provider使用InheritedWidget提供状态并监听状态,状态变化后通知到依赖的子组件进行更新,并不是子组件直接监听状态)。Getx依赖注入需要依赖它的路由管理,否则无法及时移除不需要的注入对象。当然也可以自定义NavigatorObserver来手动感知界面生命周期,从而处理不需要的注入对象。