【源码篇】Flutter GetX深度剖析 | 我们终将走出自己的路(万字图文)
前言
人心中的成见是一座大山,任你怎么努力都休想搬动。
这是电影《哪吒》里申公豹说的一句话,也是贯彻整部电影的一个主题;或许这句话引起了太多人的共鸣:35岁职场危机,大厂卡本科学历,无房无车结婚难等等,所以,这句话也经常被人提起。
同时,因为GetX作者的一些言论,也让一些成见一直伴随着GetX这个框架。
我写这篇文章,并不是为GetX正名
- 我自问自己并不是任何一个状态框架的死忠者,Provider和Bloc,我写了相关使用、原理剖析文章和相关代码生成插件
- 在我心中,这类框架并没有多么神秘
- 因为对其原理较熟,上手使用是一件较为容易的事,所以切换相关框架没有多大的时间成本
- 所以,我无需去做一个卫道者
GetX整体设计,有不少优秀点思想,我希望将这些优秀设计思路展现给大家;或许会对你设计自己的框架有一些帮助,同时也是对自己思考历程的一个记录。
前置知识
在说GetX设计思想之前,需要先介绍几个知识,在Flutter茁壮发展的历程里,他们都留下了浓墨重彩的一笔
InheritedWidget
不得不说,这个控件真的是一个神奇控件,它就仿佛是一把神兵利器
- 宝刀屠龙,号令天下,莫敢不从,倚天不出,谁与争锋
- 倚天剑,剑藏《九阴真经》
- 屠龙刀,刀藏《降龙十八掌》、《武穆遗书》
InheritedWidget这把神兵藏有什么?
- 依赖节点,数据传输
- 定点刷新机制
数据传输
InheritedWidget是我们统称的一个控件名,精髓还是InheritedElement,InheritedWidget的数据传递,来看下存和取这俩个过程
存数据
- InheritedWidget存数据,是一个比较简单的操作,存储在InheritedElement中即可
- class TransferDataWidget extends InheritedWidget {
- TransferDataWidget({required Widget child}) : super(child: child);
-
- @override
- bool updateShouldNotify(InheritedWidget oldWidget) => false;
-
- @override
- InheritedElement createElement() => TransferDataElement(this);
- }
-
- class TransferDataElement extends InheritedElement {
- TransferDataElement(InheritedWidget widget) : super(widget);
-
- ///随便初始化什么, 设置只读都行
- String value = '传输数据';
- }
取数据
- 只要是 TransferDataWidget(上面InheritedWidget的子类) 的子节点,通过子节点的BuildContext(Element是BuildContext的实现类),都可以无缝的取数据
- var transferDataElement = context.getElementForInheritedWidgetOfExactType<TransferDataWidget>()
- as TransferDataElement?;
- var msg = transferDataElement.value;
可以发现,我们只需要通过Element的getElementForInheritedWidgetOfExactType方法,就可以拿到父节点的TransferDataElement实例(必须继承InheritedElement)
拿到实例后,自然就可以很简单的拿到相应数据了
原理
- 可以发现我们是拿到了XxxInheritedElement实例,继而拿到了储存的值,所以关键在 getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() 这个方法
- 代码很简单,就是从 _inheritedWidgets这个map里取值,泛型T是key
- abstract class Element extends DiagnosticableTree implements BuildContext {
-
- Map<Type, InheritedElement>? _inheritedWidgets;
-
- @override
- InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
- assert(_debugCheckStateIsActiveForAncestorLookup());
- final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
- return ancestor;
- }
-
- ...
- }
- 接下来只要搞清楚 _inheritedWidgets 是怎么存值,那么一切都会明朗
- abstract class ComponentElement extends Element {
-
- @mustCallSuper
- void mount(Element? parent, dynamic newSlot) {
- ...
- _updateInheritance();
- }
-
- void _updateInheritance() {
- assert(_lifecycleState == _ElementLifecycle.active);
- _inheritedWidgets = _parent?._inheritedWidgets;
- }
-
- ...
- }
-
- abstract class ProxyElement extends ComponentElement {
- ...
- }
-
- class InheritedElement extends ProxyElement {
- InheritedElement(InheritedWidget widget) : super(widget);
-
- @override
- void _updateInheritance() {
- assert(_lifecycleState == _ElementLifecycle.active);
- final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
- if (incomingWidgets != null)
- _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
- else
- _inheritedWidgets = HashMap<Type, InheritedElement>();
- _inheritedWidgets![widget.runtimeType] = this;
- }
- }
整体上逻辑还是比较清晰
- 遇到某个节点为 InheritedWidget 时,会将父节点的 _inheritedWidgets 变量给 incomingWidgets 这个临时变量
- incomingWidgets 为空:为父类Element的 _inheritedWidgets 变量, 实例了一个map对象
- incomingWidgets 不为空:将父节点_inheritedWidgets 所有数据深拷贝,返回一个全新的Map实例(含有父节点 _inheritedWidgets 所有数据),赋值给父类Element的 _inheritedWidgets 变量
- 将自身实例赋值给父类Element的 _inheritedWidgets 变量,key为其widget的runtimeType
为什么任何一个Widget的Element实例的 _inheritedWidgets 变量,可直接拿到父节点InheritedElement实例?
- Element中做了一个父节点向子节点赋值的操作:整个数据传输链清晰了
- abstract class Element extends DiagnosticableTree implements BuildContext {
-
- Map<Type, InheritedElement>? _inheritedWidgets;
-
- void _updateInheritance() {
- assert(_lifecycleState == _ElementLifecycle.active);
- _inheritedWidgets = _parent?._inheritedWidgets;
- }
-
- ...
- }
- 图示
刷新机制
InheritedElement和Element之间有一些交互,实际上自带了一套刷新机制
- InheritedElement存子节点Element: _dependents,这个变量是用来存储,需要刷新的子Element
- class InheritedElement extends ProxyElement {
- InheritedElement(InheritedWidget widget) : super(widget);
-
- final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
-
- @protected
- void setDependencies(Element dependent, Object? value) {
- _dependents[dependent] = value;
- }
-
- @protected
- void updateDependencies(Element dependent, Object? aspect) {
- setDependencies(dependent, null);
- }
- }
- InheritedElement刷新子Element
- notifyClients这个方法就是将 _dependents 存储的Element,全部拿了出来,传入notifyDependent
- 在notifyDependent方法中,传入Element调用了自身didChangeDependencies()方法
- Element的didChangeDependencies() 方法会调用 markNeedsBuild() ,来刷新自身
- class InheritedElement extends ProxyElement {
- InheritedElement(InheritedWidget widget) : super(widget);
-
- final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
-
- @protected
- void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
- dependent.didChangeDependencies();
- }
-
- @override
- void notifyClients(InheritedWidget oldWidget) {
- for (final Element dependent in _dependents.keys) {
- ...
- notifyDependent(oldWidget, dependent);
- }
- }
- }
-
- abstract class Element extends DiagnosticableTree implements BuildContext {
- ...
-
- @mustCallSuper
- void didChangeDependencies() {
- assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
- assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
- markNeedsBuild();
- }
-
- ...
- }
InheritedWidget的子节点是怎么将自身Element
添加到InheritedElement的_dependents变量里的呢?
- Element里面有个 dependOnInheritedElement 方法
- Element中dependOnInheritedElement方法,会传入InheritedElement实例 ancestor
- ancestor调用updateDependencies方法,将自身的Element实例传入
- InheritedElement中就将这个Element,添加到_dependents变量中了
- abstract class Element extends DiagnosticableTree implements BuildContext {
- ...
-
- @override
- InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
- assert(ancestor != null);
- _dependencies ??= HashSet<InheritedElement>();
- _dependencies!.add(ancestor);
- ancestor.updateDependencies(this, aspect);
- return ancestor.widget;
- }
-
- ...
- }
-
- class InheritedElement extends ProxyElement {
- InheritedElement(InheritedWidget widget) : super(widget);
-
- final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
-
- @protected
- void setDependencies(Element dependent, Object? value) {
- _dependents[dependent] = value;
- }
-
- @protected
- void updateDependencies(Element dependent, Object? aspect) {
- setDependencies(dependent, null);
- }
- }
- dependOnInheritedElement该方法的使用也很简单
- 一般来说在某个Widget使用 getElementForInheritedWidgetOfExactType 获取父节点的 InheritedElement
- 然后将其传入 dependOnInheritedElement 方法中即可
- // 举例
- var inheritedElement = context
- .getElementForInheritedWidgetOfExactType<ChangeNotifierEasyP<T>>()
- as EasyPInheritedElement<T>?;
- context.dependOnInheritedElement(inheritedElement);
Provider核心原理,就是采用了InheritedWidget这种刷新机制
想详细了解Provider相关原理,可参考下面文章
图示
- 来看下InheritedWidget刷新机制的图示
路由小知识
- 路由Navigator中基本都是些操作路由的静态方法,NavigatorState是实现的具体逻辑
大家在使用InheritedWidget获取数据的时候,或许有过这样一种困扰:A页面 ---> B页面 ---> C页面
如果我在A页面使用InheritedWidget储存了数据,跳转到B页面或者C页面,会发现使用context获取不到A页面的InheritedElement
这侧面证明了Navigator路由跳转:A页面跳转B页面,B页面并不是A页面的子节点
- 大致结构:可勉强理解为,Navigator是所有页面父节点,页面与页面之间是平级关系
这里我画了下大致结构,如有偏差,请务必指出来,我会尽快修改
关于Flutter路由原理解析,可参考此文章(作者为啥现在不更文了呢 ~~):Flutter 路由原理解析
思考
InheritedWidget为我们带了很多便利
- 可以在一个Widget树范围,获取到我们想要InheritedElement(通过泛型区分即可)
- InheritedElement和Element各种交互,也实现了一套极其简洁的刷新机制
- 进行一些深度封装,甚至可以无缝的管理很多控件的资源释放
但是,Element提供的获取InheritedElement的方式,终究和路由机制无法很好的结合;这也模块设计无法避免的事情,或许某些模块设计的最优解,很难顾忌到其它模快的一些机制
InheritedWidget这把神兵利器,在我们学习Flutter历程中给予了很多帮助
- 但是,当需求逐渐复杂,你的技术不断精进
- 屠龙刀这把神兵,或许渐渐的,不太适合你了
- 有时候,它甚至制约了你的出招
- 一位制造神兵的铁匠,在他心中,最好的神兵利器,或许永远是下一把
大部分的状态管理框架,将界面层和逻辑层分开,都是逻辑层来处理界面的刷新;逻辑层可以交给InheritedWidget存储管理;说明,我们自己也一定可以存储管理!
- 自己来管理逻辑层,可以摆脱Element树的约束,不用被困在Element树的父子节点中
- 在路由跳转的页面中,可以很轻松的获取上一页面,下一个页面或者上上一个页面逻辑层。
这也是GetX中一个核心思想,这并不是一个多么新颖或高深技术,但是,我这觉得这是一种思维上的突破,可以带来更多的可能
依赖注入
说明
依赖注入有如下实现方式(维基百科):
- 基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
- 基于 set 方法。实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
- 基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
- 基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。
强耦合类型的,基于构造函数
- class Test {
- String msg;
-
- Test(String msg) {
- this.msg = msg;
- }
- }
set方式
- class Test {
- String? _msg;
-
- void setMsg(String msg) {
- this._msg = msg;
- }
- }
如果在Java中,图一时方便,直接在构造函数里面传值,然后需要的值越来越多,导致需要增加该构造函数传参,因为强耦合很多类,一改构造函数,爆红一大片(Dart构造函数可选参数的特性,就没有这类问题了)
- Getx注入的GetXController都是由GetX框架自己来维护的,如果没有GetX这个中间层会是什么样的?
- 引入GetX这个中间层来管理
- 看下图,瞬间就想到了中介者模式
- 这也是控制反转的思想(创建对象的控制权本来在自己手上,现在交给了第三方)
Put
来看下GetX注入的操作
- put使用
- var controller = Get.put(XxxGetxController());
- 看看内部操作
- 哎,各种骚操作
- 主要逻辑在Inst中,Inst是GetInterface的扩展类
- class _GetImpl extends GetInterface {}
-
- final Get = _GetImpl();
-
- extension Inst on GetInterface {
- S put<S>(S dependency,
- {String? tag,
- bool permanent = false,
- InstanceBuilderCallback<S>? builder}) =>
- GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
- }
- 主要的逻辑看来还是GetInstance中
- 大家可以看看这地方单例的实现,我发现很多源码都用这种方式写的,非常简洁
- 全局的数据都是存在 _singl 中,这是个Map
- key:对象的runtimeType或者类的Type + tag
- value:_InstanceBuilderFactory类,我们传入dependedt对象会存入这个类中
- _singl 这个map存值的时候,不是用的put,而是用的putIfAbsent
- 如果map中有key和传入key相同的数据,传入的数据将不会被存储
- 也就是说相同类实例的对象,传入并不会被覆盖,只会存储第一条数据,后续被放弃
- 最后使用find方法,返回传入的实例
- class GetInstance {
- factory GetInstance() => _getInstance ??= GetInstance._();
-
- const GetInstance._();
-
- static GetInstance? _getInstance;
-
- static final Map<String, _InstanceBuilderFactory> _singl = {};
-
- 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,
- }) {
- final key = _getKey(S, name);
- _singl.putIfAbsent(
- key,
- () => _InstanceBuilderFactory<S>(
- isSingleton,
- builder,
- permanent,
- false,
- fenix,
- name,
- ),
- );
- }
-
- String _getKey(Type type, String? name) {
- return name == null ? type.toString() : type.toString() + name;
- }
-
- S find<S>({String? tag}) {
- final key = _getKey(S, tag);
- if (isRegistered<S>(tag: tag)) {
- if (_singl[key] == null) {
- if (tag == null) {
- throw 'Class "$S" is not registered';
- } else {
- throw 'Class "$S" with tag "$tag" is not registered';
- }
- }
- final i = _initDependencies<S>(name: tag);
- return i ?? _singl[key]!.getDependency() as S;
- } else {
- // ignore: lines_longer_than_80_chars
- throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
- }
- }
- }
find
- find方法还是蛮简单的,就是从map中取数据的操作
- S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
- 看下具体逻辑
- 先判断 _singl 中是否含有该key的数据,有则取,无则抛异常
- 关键代码: _singlkey!.getDependency() as S ,直接通过key去map取值就行了
- class GetInstance {
- factory GetInstance() => _getInstance ??= GetInstance._();
-
- const GetInstance._();
-
- static GetInstance? _getInstance;
-
- static final Map<String, _InstanceBuilderFactory> _singl = {};
-
- String _getKey(Type type, String? name) {
- return name == null ? type.toString() : type.toString() + name;
- }
-
- 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)) {
- if (_singl[key] == null) {
- if (tag == null) {
- throw 'Class "$S" is not registered';
- } else {
- throw 'Class "$S" with tag "$tag" is not registered';
- }
- }
- final i = _initDependencies<S>(name: tag);
- return i ?? _singl[key]!.getDependency() as S;
- } else {
- // ignore: lines_longer_than_80_chars
- throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
- }
- }
- }
GetBuilder刷新机制
使用
为了知识的连续性,此处简单的写下使用
- 逻辑层
- class GetCounterEasyLogic extends GetxController {
- var count = 0;
-
- void increase() {
- ++count;
- update();
- }
- }
- 界面
- class GetCounterEasyPage extends StatelessWidget {
- final GetCounterEasyLogic logic = Get.put(GetCounterEasyLogic());
-
- @override
- Widget build(BuildContext context) {
- return BaseScaffold(
- appBar: AppBar(title: const Text('计数器-简单式')),
- body: Center(
- child: GetBuilder<GetCounterEasyLogic>(builder: (logic) {
- return Text(
- '点击了 ${logic.count} 次',
- style: TextStyle(fontSize: 30.0),
- );
- }),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () => logic.increase(),
- child: Icon(Icons.add),
- ),
- );
- }
- }
GetBuilder
有一天,我躺在床上思考
- Obx的状态管理,GetXController实例回收是放在路由里面,在很多场景下,存在一些局限性
- 后来我想到,GetBuilder使用带泛型,这就能拿到GetxController实例,GetBuilder又是StatefulWidget
- 这样就可以使用它来回收实例,能解决很多场景下,GetXController实例无法回收的问题(不使用Getx路由)
- 我兴致冲冲的打开Getx项目,准备提PR,然后发现GetBuilder已经在dispose里面写了回收实例的操作
- 淦!
内置回收机制
- 此处精简很多代码,只展示回收机制的代码
- class GetBuilder<T extends GetxController> extends StatefulWidget {
- final GetControllerBuilder<T> builder;
- final bool global;
- final String? tag;
- final bool autoRemove;
- final T? init;
-
- const GetBuilder({
- Key? key,
- this.init,
- this.global = true,
- required this.builder,
- this.autoRemove = true,
- this.initState,
- this.tag,
- }) : super(key: key);
-
-
- @override
- GetBuilderState<T> createState() => GetBuilderState<T>();
- }
-
- class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
- with GetStateUpdaterMixin {
- T? controller;
- bool? _isCreator = false;
- VoidCallback? _remove;
- Object? _filter;
-
- @override
- void initState() {
- super.initState();
- widget.initState?.call(this);
-
- var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
-
- if (widget.global) {
- if (isRegistered) {
- controller = GetInstance().find<T>(tag: widget.tag);
- } else {
- controller = widget.init;
- GetInstance().put<T>(controller!, tag: widget.tag);
- }
- } else {
- controller = widget.init;
- controller?.onStart();
- }
-
- }
-
- @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();
-
- controller = null;
- _isCreator = null;
- _remove = null;
- _filter = null;
- }
-
-
- @override
- Widget build(BuildContext context) {
- return widget.builder(controller!);
- }
- }
代码里的逻辑还是相当清晰的,initState获取实例,dispose回收实例
- 通过GetBuilder上泛型获取相应GetXController实例
- 不存在:使用init传入的实例
- 存在:直接使用;init传入的实例无效
- autoRemove可以控制是否自动回收GetXController实例
- 默认为true:默认开启自动回收
- true:开启自动回收 false:关闭自动回收
刷新逻辑
- 这里仅保留刷新逻辑的相关代码,去掉了无需关注的代码
- mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
- void getUpdate() {
- if (mounted) setState(() {});
- }
- }
-
- class GetBuilder<T extends GetxController> extends StatefulWidget {
- final GetControllerBuilder<T> builder;
- final bool global;
- final T? init;
- final Object? id;
-
- const GetBuilder({
- Key? key,
- this.init,
- this.id,
- this.global = true,
- required this.builder,
- }) : super(key: key);
-
-
- @override
- GetBuilderState<T> createState() => GetBuilderState<T>();
- }
-
- class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
- with GetStateUpdaterMixin {
- T? controller;
-
- @override
- void initState() {
- super.initState();
- ...
-
- if (widget.global) {
- if (isRegistered) {
- controller = GetInstance().find<T>(tag: widget.tag);
- } else {
- controller = widget.init;
- GetInstance().put<T>(controller!, tag: widget.tag);
- }
- } else {
- controller = widget.init;
- controller?.onStart();
- }
-
- _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 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!);
- }
- }
关键步骤
- 通过泛型获取注入的GetXController实例
- 添加监听代码
- addListener:添加监听回调
- addListenerId:添加监听回调,必须设置id,update刷新的时候也必须写上配套的id
- 监听代码:核心代码就是getUpdate方法,方法在 GetStateUpdaterMixin 中
- getUpdate()逻辑就是 setState(),刷新当前GetBuilder
图示
Update
- 触发逻辑还是很简单的,使用update即可
- Ids:和上面的Getbuilder对应起来了,可刷新对应设置id的GetBuilder
- condition:是否刷新一个判断条件,默认为true(假设必须某个id大于3才能刷新:update(1, 2, 3, 4, index > 3) )
- abstract class GetxController extends DisposableInterface with ListNotifier {
- void update([List<Object>? ids, bool condition = true]) {
- if (!condition) {
- return;
- }
- if (ids == null) {
- refresh();
- } else {
- for (final id in ids) {
- refreshGroup(id);
- }
- }
- }
- }
- 看下关键方法 refresh(),在ListNotifier类中
- 可以发现,_updaters中泛型就是一个方法
- 在GetBuilder中添加的监听就是一个方法参数,方法体里面就是 setState()
- 齐活了!GetBuilder添加方法(方法体是setState),update遍历触发所有添加方法
- typedef GetStateUpdate = void Function();
- class ListNotifier implements Listenable {
- 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!();
- }
- }
-
- ...
- }
- 如果在update中加了id参数,会走refreshGroup方法,逻辑和refresh几乎一样,差别是对id的判断:有则执行,无则跳过
- 遍历所有ids,然后执行refreshGroup方法
- abstract class GetxController extends DisposableInterface with ListNotifier {
- void update([List<Object>? ids, bool condition = true]) {
- if (!condition) {
- return;
- }
- if (ids == null) {
- refresh();
- } else {
- for (final id in ids) {
- refreshGroup(id);
- }
- }
- }
- }
-
- class ListNotifier implements Listenable {
- HashMap<Object?, List<GetStateUpdate>>? _updatersGroupIds =
- HashMap<Object?, List<GetStateUpdate>>();
-
- 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);
- }
- }
总结
- 来看下GetBuilder刷新图示
Obx刷新机制
这套刷新机制,和我们常用的状态管理框架(provider,bloc)以及上面的GetBuilder,在使用上有一些区别
- 变量上:基础类型,实体以及列表之类的数据类型,作者都封装了一套Rx类型,快捷在数据后加obs
- 例如:RxString msg = "test".obs(var msg = "test".obs)
- 更新上:基础类型直接更新数据就行,实体需要以 .update() 的形式
- 使用上:使用这类变量,一般要加上 .value ,作者也给出一个快捷方式变量后面加个 ()
- 我不太推荐加 () 的形式,对后续维护项目人太不友好了
Obx刷新机制,最有趣应该就是变量改变后,包裹该变量的Obx会自动刷新!注意喔,仅仅是包裹该变量的Obx会刷新!其它的Obx并不会刷新。
这是怎么做到的呢?
- 实际上,实现起来很简单
- 但是,如果没有接触过这个思路,恐怕抓破头,都很难想出来,还能这么玩。。。
使用
简单的来看下使用
- logic
- class GetCounterRxLogic extends GetxController {
- var count = 0.obs;
-
- ///自增
- void increase() => ++count;
- }
- view
- class GetCounterRxPage extends StatelessWidget {
- final GetCounterRxLogic logic = Get.put(GetCounterRxLogic());
-
- @override
- Widget build(BuildContext context) {
- return BaseScaffold(
- appBar: AppBar(title: const Text('计数器-响应式')),
- body: Center(
- child: Obx(() {
- return Text(
- '点击了 ${logic.count.value} 次',
- style: TextStyle(fontSize: 30.0),
- );
- }),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () => logic.increase(),
- child: Icon(Icons.add),
- ),
- );
- }
- }
Rx类变量
此处以 RxInt 为例,来看下其内部实现
- 先来看下整型后面的拓展 obs ,这是一个扩展类,0.obs 等同 RxInt(0)
- extension IntExtension on int {
- /// Returns a `RxInt` with [this] `int` as initial value.
- RxInt get obs => RxInt(this);
- }
- 来看下RxInt:这地方明确了使用 .value 运行时,会自动返回一个当前实例,并修改相应value数值
- 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;
- }
- }
- 来看下父类 Rx<T>
- 这地方出现了一个很重要的类: _RxImpl<T>
- 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]';
- }
- }
- }
- _RxImpl<T> 类继承了 RxNotifier<T> 和 with 了 RxObjectMixin<T>
- 这个类内容是比较庞大的,主要是 RxNotifier<T> 和 RxObjectMixin<T> 内容很多
- 代码很多,先展示下完整代码;在下一个说明处会进行简化
- abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
- _RxImpl(T initial) {
- _value = initial;
- }
-
- void addError(Object error, [StackTrace? stackTrace]) {
- subject.addError(error, stackTrace);
- }
-
- Stream<R> map<R>(R mapper(T? data)) => stream.map(mapper);
-
- void update(void fn(T? val)) {
- fn(_value);
- subject.add(_value);
- }
-
- void trigger(T v) {
- var firstRebuild = this.firstRebuild;
- value = v;
- if (!firstRebuild) {
- subject.add(v);
- }
- }
- }
-
- class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
-
- mixin NotifyManager<T> {
- GetStream<T> subject = GetStream<T>();
- final _subscriptions = <GetStream, List<StreamSubscription>>{};
-
- bool get canUpdate => _subscriptions.isNotEmpty;
-
- 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);
- }
- }
-
- StreamSubscription<T> listen(
- void Function(T) onData, {
- Function? onError,
- void Function()? onDone,
- bool? cancelOnError,
- }) =>
- subject.listen(
- onData,
- onError: onError,
- onDone: onDone,
- cancelOnError: cancelOnError ?? false,
- );
-
- void close() {
- _subscriptions.forEach((getStream, _subscriptions) {
- for (final subscription in _subscriptions) {
- subscription.cancel();
- }
- });
-
- _subscriptions.clear();
- subject.close();
- }
- }
-
- mixin RxObjectMixin<T> on NotifyManager<T> {
- late T _value;
-
- void refresh() {
- subject.add(value);
- }
-
- T call([T? v]) {
- if (v != null) {
- value = v;
- }
- return value;
- }
-
- bool firstRebuild = true;
-
- String get string => value.toString();
-
- @override
- String toString() => value.toString();
-
- dynamic toJson() => value;
-
- @override
- bool operator ==(dynamic o) {
- if (o is T) return value == o;
- if (o is RxObjectMixin<T>) return value == o.value;
- return false;
- }
-
- @override
- int get hashCode => _value.hashCode;
-
- set value(T val) {
- if (subject.isClosed) return;
- if (_value == val && !firstRebuild) return;
- firstRebuild = false;
- _value = val;
-
- subject.add(_value);
- }
-
- T get value {
- if (RxInterface.proxy != null) {
- RxInterface.proxy!.addListener(subject);
- }
- return _value;
- }
-
- Stream<T?> get stream => subject.stream;
-
- void bindStream(Stream<T> stream) {
- final listSubscriptions =
- _subscriptions[subject] ??= <StreamSubscription>[];
- listSubscriptions.add(stream.listen((va) => value = va));
- }
- }
- 简化 _RxImpl<T>,上面内容太多了,我这地方简化下,把需要关注的内容展示出来:此处有几个需要重点关注的点
- RxInt是一个内置callback的数据类型(GetStream)
- RxInt的value变量改变的时候(set value),会触发subject.add(_value),内部逻辑是自动刷新操作
- 获取RxInt的value变量的时候(get value),会有一个添加监听的操作,这个灰常重要!
- abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
-
- void update(void fn(T? val)) {
- fn(_value);
- subject.add(_value);
- }
- }
-
- class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
-
- mixin NotifyManager<T> {
- GetStream<T> subject = GetStream<T>();
- final _subscriptions = <GetStream, List<StreamSubscription>>{};
-
- bool get canUpdate => _subscriptions.isNotEmpty;
-
- 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);
- }
- }
- }
-
- mixin RxObjectMixin<T> on NotifyManager<T> {
- late T _value;
-
- void refresh() {
- subject.add(value);
- }
-
- set value(T val) {
- if (subject.isClosed) return;
- if (_value == val && !firstRebuild) return;
- firstRebuild = false;
- _value = val;
-
- subject.add(_value);
- }
-
- T get value {
- if (RxInterface.proxy != null) {
- RxInterface.proxy!.addListener(subject);
- }
- return _value;
- }
- }
- 为啥GetStream的add会有刷新操作:删了很多代码,保留了重点代码
- 调用add方法时候,会调用 _notifyData 方法
- _notifyData 方法中,会遍历 _onData 列表,根据条件会执行其泛型的 _data 的方法
- 我猜,_data 中的方法体,十有八九在某个地方肯定添加了 setState()
- class GetStream<T> {
- GetStream({this.onListen, this.onPause, this.onResume, this.onCancel});
- List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];
-
- FutureOr<void> addSubscription(LightSubscription<T> subs) async {
- if (!_isBusy!) {
- return _onData!.add(subs);
- } else {
- await Future.delayed(Duration.zero);
- return _onData!.add(subs);
- }
- }
-
- void _notifyData(T data) {
- _isBusy = true;
- for (final item in _onData!) {
- if (!item.isPaused) {
- item._data?.call(data);
- }
- }
- _isBusy = false;
- }
-
- T? _value;
-
- T? get value => _value;
-
- void add(T event) {
- assert(!isClosed, 'You cannot add event to closed Stream');
- _value = event;
- _notifyData(event);
- }
- }
-
- typedef OnData<T> = void Function(T data);
-
- class LightSubscription<T> extends StreamSubscription<T> {
- OnData<T>? _data;
- }
- 图示,先来看下,Rx类具有的功能
- get value 添加监听
- set value 执行已添加的监听
Obx刷新机制
Obx最大的特殊之处,应该就是使用它的时候,不需要加泛型且能自动刷新,这是怎么做到的呢?
- Obx:代码并不多,但是皆有妙用
- Obx继承ObxWidget,ObxWidget实际上也是一个StatefulWidget
- _ObxState 中代码就是核心代码了
- class Obx extends ObxWidget {
- final WidgetCallback builder;
-
- const Obx(this.builder);
-
- @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> {
- RxInterface? _observer;
- late StreamSubscription subs;
-
- _ObxState() {
- _observer = RxNotifier();
- }
-
- @override
- void initState() {
- subs = _observer!.listen(_updateTree, cancelOnError: false);
- super.initState();
- }
-
- void _updateTree(_) {
- if (mounted) {
- setState(() {});
- }
- }
-
- @override
- void dispose() {
- subs.cancel();
- _observer!.close();
- super.dispose();
- }
-
- Widget get notifyChilds {
- final observer = RxInterface.proxy;
- RxInterface.proxy = _observer;
- final result = widget.build();
- if (!_observer!.canUpdate) {
- 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;
- }
-
- @override
- Widget build(BuildContext context) => notifyChilds;
- }
添加监听
一个控件想刷新,肯定有添加监听的逻辑,再在某个地方手动触发
- 看下_ObxState类在哪添加监听:只展示监听添加的代码
- _ObxState初始化的时候,会实例化一个 RxNotifier() 对象,使用 _observer变量接受:这个操作很重要
- initState中做了一个比较关键的操作,_observer的listener方法中,将 _updateTree方法传进去了,这个方法中的逻辑体就是 setState()
- class _ObxState extends State<ObxWidget> {
- RxInterface? _observer;
- late StreamSubscription subs;
-
- _ObxState() {
- _observer = RxNotifier();
- }
-
- @override
- void initState() {
- subs = _observer!.listen(_updateTree, cancelOnError: false);
- super.initState();
- }
-
- void _updateTree(_) {
- if (mounted) {
- setState(() {});
- }
- }
- }
上述很多逻辑和 RxNotifier 类相关,来看下这个类
- RxNotifier 这个类,内部会实例一个 GetStream<T>() 对象,然后赋值给 subject
- 上面赋值 _updateTree 方法被传入的 GetStream<T>() 类中,最终添加 _onData 该列表变量中
- 瞟一眼 _notifyData方法,是不是遍历执行了 _onData 列表中item的方法( item. _data?.call(data) )
- class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
-
- mixin NotifyManager<T> {
- GetStream<T> subject = GetStream<T>();
- final _subscriptions = <GetStream, List<StreamSubscription>>{};
-
- bool get canUpdate => _subscriptions.isNotEmpty;
-
- StreamSubscription<T> listen(
- void Function(T) onData, {
- Function? onError,
- void Function()? onDone,
- bool? cancelOnError,
- }) =>
- subject.listen(
- onData,
- onError: onError,
- onDone: onDone,
- cancelOnError: cancelOnError ?? false,
- );
- }
-
- class GetStream<T> {
- void Function()? onListen;
- void Function()? onPause;
- void Function()? onResume;
- FutureOr<void> Function()? onCancel;
-
- GetStream({this.onListen, this.onPause, this.onResume, this.onCancel});
- List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];
-
- FutureOr<void> addSubscription(LightSubscription<T> subs) async {
- if (!_isBusy!) {
- return _onData!.add(subs);
- } else {
- await Future.delayed(Duration.zero);
- return _onData!.add(subs);
- }
- }
-
- int? get length => _onData?.length;
-
- bool get hasListeners => _onData!.isNotEmpty;
-
- void _notifyData(T data) {
- _isBusy = true;
- for (final item in _onData!) {
- if (!item.isPaused) {
- item._data?.call(data);
- }
- }
- _isBusy = false;
- }
-
- 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;
- }
- }
- 上面代码流程有一点绕,下面画了一个图,希望对各位有所帮助
监听转移
在_ObxState类中做了一个很重要,监听对象转移的操作
_observer中的对象已经拿到了Obx控件内部的setState方法,现在需要将它转移出去啦!
- 下面贴下将 _observer 中对象转移出去的代码:主要的逻辑就是在 notifyChilds 方法中
- RxInterface 类中有个 proxy 静态变量,这个变量十分重要,他是一个中转变量!
- `class _ObxState extends State<ObxWidget> {
- RxInterface? _observer;
-
- _ObxState() {
- _observer = RxNotifier();
- }
-
- Widget get notifyChilds {
- final observer = RxInterface.proxy;
- RxInterface.proxy = _observer;
- final result = widget.build();
- if (!_observer!.canUpdate) {
- 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;
- }
-
- @override
- Widget build(BuildContext context) => notifyChilds;
- }
-
- 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});
- }
notifyChilds中的几行代码都有深意,一行行的解读下
- final observer = RxInterface.proxy:RxInterface.proxy正常情况为空,但是,可能作为中间变量暂存对象的情况,现在暂时将他的对象取出来,存在observer变量中
- RxInterface.proxy = _observer:将我们在 _ObxState类中实例化的 RxNotifier() 对象的地址,赋值给了RxInterface.proxy
- 注意:这里,RxInterface.proxy中 RxNotifier() 实例,有当前Obx控件的setState() 方法
- final result = widget.build():这个赋值相当重要了!调用我们在外部传进的Widget
- 如果这个Widget中有响应式变量,那么一定会调用该变量中获取 get value
- 还记得get value的代码吗?
- ```dart
- mixin RxObjectMixin<T> on NotifyManager<T> {
- late T _value;
- T get value {
- if (RxInterface.proxy != null) {
- RxInterface.proxy!.addListener(subject);
- }
- return _value;
- }
- }
- mixin NotifyManager<T> {
- GetStream<T> subject = GetStream<T>();
- }
- ```
- 终于建立起联系了,将变量中 GetStream 实例,添加到了Obx中的 RxNotifier() 实例;RxNotifier() 实例中有一个 subject(GetStream ) 实例,Rx类型中数据变化会触发 subject 变化,最终刷新Obx
- ```dart
- mixin NotifyManager<T> {
- GetStream<T> subject = GetStream<T>();
- final _subscriptions = <GetStream, List<StreamSubscription>>{};
- bool get canUpdate => _subscriptions.isNotEmpty;
- void addListener(GetStream<T> rxGetx) {
- if (!_subscriptions.containsKey(rxGetx)) {
- //重点 GetStream中listen方法是用来添加监听方法的,add的时候会刷新监听方法
- final subs = rxGetx.listen((data) {
- if (!subject.isClosed) subject.add(data);
- });
- final listSubscriptions =
- _subscriptions[rxGetx] ??= <StreamSubscription>[];
- listSubscriptions.add(subs);
- }
- }
- }
- ```
- if (!_observer!.canUpdate) {}:这个判断就很简单了,如果我们传入的Widget中没有Rx类型变量, _subscriptions数组就会为空,这个判断就会过不了
- RxInterface.proxy = observer:将RxInterface.proxy中原来的值,重新赋给自己,至此 _ObxState 中的 _observer对象地址,进行了一番奇幻旅游后,结束了自己的使命
图示
总结
Obx的刷新机制,还是蛮有有趣的
- Rx变量改变,自动刷新包裹其变量Obx控件,其它的Obx控件并不会刷新
- 使用Obx控件,不需要写泛型!牛批!
但是,我认为Obx刷新机制,也是有着自身的缺陷的,从其实现原理上看,这是无法避免的
- 因为Obx的自动刷新,必须需要每一个变量都自带监听触发机制;所以,所有的基础类型,实体以及列表,都需要重新封装,这会造成很严重的使用影响:变量的赋值,类型标定,刷新都很正常写法有差异,不熟悉该写法的人,看了后,会很难受
- 因为对所有类型重新封装,经过上面的代码回溯,大家也发现,封装类型的代码相当多;封装类型占用资源肯定要比dart自带类型的大(这个问题可以避免:封装一个响应式的变量,并不一定需要很多代码,下面我给出了一个封装参考)
手搓一个状态管理框架
GetX内置了俩套状态管理机制,这边也会按照其刷新机制,手搓俩套出来
我会用极其简单的代码,再现俩套经典的机制
依赖注入
- 在做刷新机制前,首先必须写一个依赖注入的类,我们需要自己管理逻辑层的那些实例
- 我这边写了一个极其简单,仅实现三种基础功能:注入,获取,删除
- ///依赖注入,外部可将实例,注入该类中,由该类管理
- class Easy {
- ///注入实例
- static T put<T>(T dependency, {String? tag}) =>
- _EasyInstance().put(dependency, tag: tag);
-
- ///获取注入的实例
- static T find<T>({String? tag, String? key}) =>
- _EasyInstance().find<T>(tag: tag, key: key);
-
- ///删除实例
- static bool delete<T>({String? tag, String? key}) =>
- _EasyInstance().delete<T>(tag: tag, key: key);
- }
-
- ///具体逻辑
- class _EasyInstance {
- factory _EasyInstance() => _instance ??= _EasyInstance._();
-
- static _EasyInstance? _instance;
-
- _EasyInstance._();
-
- static final Map<String, _InstanceInfo> _single = {};
-
- ///注入实例
- T put<T>(T dependency, {String? tag}) {
- final key = _getKey(T, tag);
- //只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
- _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
- return find<T>(tag: tag);
- }
-
- ///获取注入的实例
- T find<T>({String? tag, String? key}) {
- final newKey = key ?? _getKey(T, tag);
- var info = _single[newKey];
-
- if (info?.value != null) {
- return info!.value;
- } else {
- throw '"$T" not found. You need to call "Easy.put($T())""';
- }
- }
-
- ///删除实例
- bool delete<T>({String? tag, String? key}) {
- final newKey = key ?? _getKey(T, tag);
- if (!_single.containsKey(newKey)) {
- print('Instance "$newKey" already removed.');
- return false;
- }
-
- _single.remove(newKey);
- print('Instance "$newKey" deleted.');
- return true;
- }
-
- String _getKey(Type type, String? name) {
- return name == null ? type.toString() : type.toString() + name;
- }
- }
-
- class _InstanceInfo<T> {
- _InstanceInfo(this.value);
-
- T value;
- }
- 自定义一个监听类,这个类很重要,下面俩种机制都需要用到
- ///自定义个监听触发类
- class EasyXNotifier {
- List<VoidCallback> _listeners = [];
-
- void addListener(VoidCallback listener) {
- _listeners.add(listener);
- }
-
- void removeListener(VoidCallback listener) {
- for (final entry in _listeners) {
- if (entry == listener) {
- _listeners.remove(entry);
- return;
- }
- }
- }
-
- void dispose() {
- _listeners.clear();
- }
-
- void notify() {
- if (_listeners.isEmpty) return;
-
- for (final entry in _listeners) {
- try {
- entry.call();
- } catch (e) {
- print(e.toString());
- }
- }
- }
- }
EasyBuilder
实现
- 该模式需要自定义一个基类
- 我这地方写的极简,相关生命周期都没加,这个加起来也很容易,定义各个生命周期,在Builder控件里面触发,就可以了
- 为了代码简洁,这个暂且不表
- class EasyXController {
- EasyXNotifier xNotifier = EasyXNotifier();
-
- ///刷新控件
- void update() {
- xNotifier.notify();
- }
- }
- 再来看看最核心的EasyBuilder控件:这就搞定了!
- 实现代码写的极其简单,希望大家思路能有所明晰
- ///刷新控件,自带回收机制
- class EasyBuilder<T extends EasyXController> extends StatefulWidget {
- final Widget Function(T logic) builder;
-
- final String? tag;
- final bool autoRemove;
-
- const EasyBuilder({
- Key? key,
- required this.builder,
- this.autoRemove = true,
- this.tag,
- }) : super(key: key);
-
- @override
- _EasyBuilderState<T> createState() => _EasyBuilderState<T>();
- }
-
- class _EasyBuilderState<T extends EasyXController>
- extends State<EasyBuilder<T>> {
- late T controller;
-
- @override
- void initState() {
- super.initState();
-
- controller = Easy.find<T>(tag: widget.tag);
- controller.xNotifier.addListener(() {
- if (mounted) setState(() {});
- });
- }
-
- @override
- void dispose() {
- if (widget.autoRemove) {
- Easy.delete<T>(tag: widget.tag);
- }
- controller.xNotifier.dispose();
-
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return widget.builder(controller);
- }
- }
使用
- 使用很简单,先看下逻辑层
- class EasyXCounterLogic extends EasyXController {
- var count = 0;
-
- void increase() {
- ++count;
- update();
- }
- }
- 界面层
- class EasyXCounterPage extends StatelessWidget {
- final EasyXCounterLogic logic = Easy.put(EasyXCounterLogic());
-
- @override
- Widget build(BuildContext context) {
- return BaseScaffold(
- appBar: AppBar(title: const Text('EasyX-自定义EasyBuilder刷新机制')),
- body: Center(
- child: EasyBuilder<EasyXCounterLogic>(builder: (logic) {
- return Text(
- '点击了 ${logic.count} 次',
- style: TextStyle(fontSize: 30.0),
- );
- }),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () => logic.increase(),
- child: Icon(Icons.add),
- ),
- );
- }
- }
- 效果图
Ebx:自动刷新机制
自动刷新机制,因为没加泛型,所以无法确定自己内部使用了哪个注入实例,Getx中是在路由里面去回收这些实例的,但是,如果你没使用GetX的路由,又用Obx,你会发现,GetXController居然无法自动回收!!!
此处针对该场景,我会给出一种解决方案
实现
- 在自动刷新的机制中,需要将基础类型进行封装
- 主要逻辑在Rx<T>中
- set value 和 get value是关键
- ///拓展函数
- extension IntExtension on int {
- RxInt get ebs => RxInt(this);
- }
-
- extension StringExtension on String {
- RxString get ebs => RxString(this);
- }
-
- extension DoubleExtension on double {
- RxDouble get ebs => RxDouble(this);
- }
-
- extension BoolExtension on bool {
- RxBool get ebs => RxBool(this);
- }
-
- ///封装各类型
- class RxInt extends Rx<int> {
- RxInt(int initial) : super(initial);
-
- RxInt operator +(int other) {
- value = value + other;
- return this;
- }
-
- RxInt operator -(int other) {
- value = value - other;
- return this;
- }
- }
-
- class RxDouble extends Rx<double> {
- RxDouble(double initial) : super(initial);
-
- RxDouble operator +(double other) {
- value = value + other;
- return this;
- }
-
- RxDouble operator -(double other) {
- value = value - other;
- return this;
- }
- }
-
- class RxString extends Rx<String> {
- RxString(String initial) : super(initial);
- }
-
- class RxBool extends Rx<bool> {
- RxBool(bool initial) : super(initial);
- }
-
- ///主体逻辑
- class Rx<T> {
- EasyXNotifier subject = EasyXNotifier();
-
- Rx(T initial) {
- _value = initial;
- }
-
- late T _value;
-
- bool firstRebuild = true;
-
- String get string => value.toString();
-
- @override
- String toString() => value.toString();
-
- set value(T val) {
- if (_value == val && !firstRebuild) return;
- firstRebuild = false;
- _value = val;
-
- subject.notify();
- }
-
- T get value {
- if (RxEasy.proxy != null) {
- RxEasy.proxy!.addListener(subject);
- }
- return _value;
- }
- }
- 需要写一个非常重要的中转类,这个也会储存响应式变量的监听对象
- 这个类有着非常核心的逻辑:他将响应式变量和刷新控件关联起来了!
- class RxEasy {
- EasyXNotifier easyXNotifier = EasyXNotifier();
-
- Map<EasyXNotifier, String> _listenerMap = {};
-
- bool get canUpdate => _listenerMap.isNotEmpty;
-
- static RxEasy? proxy;
-
- void addListener(EasyXNotifier notifier) {
- if (!_listenerMap.containsKey(notifier)) {
- //变量监听中刷新
- notifier.addListener(() {
- //刷新ebx中添加的监听
- easyXNotifier.notify();
- });
- //添加进入map中
- _listenerMap[notifier] = '';
- }
- }
- }
- 刷新控件Ebx
- typedef WidgetCallback = Widget Function();
-
- class Ebx extends StatefulWidget {
- const Ebx(this.builder, {Key? key}) : super(key: key);
-
- final WidgetCallback builder;
-
- @override
- _EbxState createState() => _EbxState();
- }
-
- class _EbxState extends State<Ebx> {
- RxEasy _rxEasy = RxEasy();
-
- @override
- void initState() {
- super.initState();
-
- _rxEasy.easyXNotifier.addListener(() {
- if (mounted) setState(() {});
- });
- }
-
- Widget get notifyChild {
- final observer = RxEasy.proxy;
- RxEasy.proxy = _rxEasy;
- final result = widget.builder();
- if (!_rxEasy.canUpdate) {
- throw 'Widget lacks Rx type variables';
- }
- RxEasy.proxy = observer;
- return result;
- }
-
- @override
- Widget build(BuildContext context) {
- return notifyChild;
- }
-
- @override
- void dispose() {
- _rxEasy.easyXNotifier.dispose();
-
- super.dispose();
- }
- }
- 在上面说了,在自动刷新机制中,自动回收依赖实例是个蛋筒的问题,此处我写了一个回收控件,可以解决此问题
- 使用时,必须套一层了;如果大家有更好的思路,麻烦在评论里告知
- class EasyBindWidget extends StatefulWidget {
- const EasyBindWidget({
- Key? key,
- this.bind,
- this.tag,
- this.binds,
- this.tags,
- required this.child,
- }) : assert(
- binds == null || tags == null || binds.length == tags.length,
- 'The binds and tags arrays length should be equal\\n'
- 'and the elements in the two arrays correspond one-to-one',
- ),
- super(key: key);
-
- final Object? bind;
- final String? tag;
-
- final List<Object>? binds;
- final List<String>? tags;
-
- final Widget child;
-
- @override
- _EasyBindWidgetState createState() => _EasyBindWidgetState();
- }
-
- class _EasyBindWidgetState extends State<EasyBindWidget> {
- @override
- Widget build(BuildContext context) {
- return widget.child;
- }
-
- @override
- void dispose() {
- _closeController();
- _closeControllers();
-
- super.dispose();
- }
-
- void _closeController() {
- if (widget.bind == null) {
- return;
- }
-
- var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
- Easy.delete(key: key);
- }
-
- void _closeControllers() {
- if (widget.binds == null) {
- return;
- }
-
- for (var i = 0; i < widget.binds!.length; i++) {
- var type = widget.binds![i].runtimeType.toString();
-
- if (widget.tags == null) {
- Easy.delete(key: type);
- } else {
- var key = type + (widget.tags?[i] ?? '');
- Easy.delete(key: key);
- }
- }
- }
- }
使用
- 逻辑层,这次,咱们连基类都不需要写
- class EasyXEbxCounterLogic {
- RxInt count = 0.ebs;
-
- ///自增
- void increase() => ++count;
- }
- 界面层:页面顶节点套了一个EasyBindWidget,可以保证依赖注入实例可以自动回收
- class EasyXEbxCounterPage extends StatelessWidget {
- final EasyXEbxCounterLogic logic = Easy.put(EasyXEbxCounterLogic());
-
- @override
- Widget build(BuildContext context) {
- return EasyBindWidget(
- bind: logic,
- child: BaseScaffold(
- appBar: AppBar(title: const Text('EasyX-自定义Ebx刷新机制')),
- body: Center(
- child: Ebx(() {
- return Text(
- '点击了 ${logic.count.value} 次',
- style: TextStyle(fontSize: 30.0),
- );
- }),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () => logic.increase(),
- child: Icon(Icons.add),
- ),
- ),
- );
- }
- }
- 效果图
总结
这俩种刷新模式,含金量高的,应该还是自动刷新的机制,思路很有趣,响应式变量和刷新控件通过静态变量的形式建立起联系,cool!又是一种骚操作!
这俩套状态管理机制,我都给出了对依赖注入对象,自动回收的解决方案,希望对大家的思路有所启迪。
最后
终于把最后一篇GetX的原理剖析写完了(只针对GetX状态管理这部分内容),了了一桩心事。。。
- 有些流程比较绕,特地画了一些图,图文并茂总会让人心情愉悦嘛......
如果大家认真看完了整片文章,可能会发现:状态管理+依赖注入,可以使得使用场景大大的被拓展
- GetBuilder的自动回收就是借助依赖注入,无缝获取注入实例,从而实现自动回收的操作
- 而且GetBuilder还无需额外传参数!
整篇文章写下来,我真的尽力了
- 从InheritedWidget到路由
- 然后到依赖注入
- 再就是俩种状态框架原理剖析
- 最后依据俩种刷新机制,手搓俩套状态管理框架
也算是层层递进的将其中的知识,一点点的展示在大家的面前,希望可以帮到各位!!!
系列文章 + 相关地址