【源码篇】Flutter GetX深度剖析 | 我们终将走出自己的路(万字图文)

image-20210627221006498

前言

人心中的成见是一座大山,任你怎么努力都休想搬动。

这是电影《哪吒》里申公豹说的一句话,也是贯彻整部电影的一个主题;或许这句话引起了太多人的共鸣: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() 这个方法
    • 代码很简单,就是从 _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;
    }
}

整体上逻辑还是比较清晰

  1. 遇到某个节点为 InheritedWidget 时,会将父节点的 _inheritedWidgets 变量给 incomingWidgets 这个临时变量
    1. incomingWidgets 为空:为父类Element的 _inheritedWidgets 变量, 实例了一个map对象
    2. incomingWidgets 不为空:将父节点_inheritedWidgets 所有数据深拷贝,返回一个全新的Map实例(含有父节点 _inheritedWidgets 所有数据),赋值给父类Element的 _inheritedWidgets 变量
  2. 将自身实例赋值给父类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;
    }

	...
}
  • 图示

InheritedWidget存取数据

刷新机制

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刷新机制的图示

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依赖注入-前

  • 引入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的数据,有则取,无则抛异常
    • 关键代码: _singl[key]!.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回收实例

  1. 通过GetBuilder上泛型获取相应GetXController实例
    • 不存在:使用init传入的实例
    • 存在:直接使用;init传入的实例无效
  2. 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!);
  }
}

关键步骤

  1. 通过泛型获取注入的GetXController实例
  2. 添加监听代码
    • addListener:添加监听回调
    • addListenerId:添加监听回调,必须设置id,update刷新的时候也必须写上配套的id
  3. 监听代码:核心代码就是getUpdate方法,方法在 GetStateUpdaterMixin 中
    • getUpdate()逻辑就是 setState(),刷新当前GetBuilder

图示

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刷新图示

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
    • 这地方出现了一个很重要的类: _RxImpl
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 类继承了 RxNotifier 和 with 了 RxObjectMixin
  • 这个类内容是比较庞大的,主要是 RxNotifier 和 RxObjectMixin 内容很多
  • 代码很多,先展示下完整代码;在下一个说明处会进行简化
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,上面内容太多了,我这地方简化下,把需要关注的内容展示出来:此处有几个需要重点关注的点
    • 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 执行已添加的监听

Rx类变量

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() 对象,然后赋值给 subject
  • 上面赋值 _updateTree 方法被传入的 GetStream() 类中,最终添加 _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;
  }
}
  • 上面代码流程有一点绕,下面画了一个图,希望对各位有所帮助

Obx监听添加

监听转移

在_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的代码吗?

      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

      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监听转移

总结

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),
      ),
    );
  }
}
  • 效果图

easy_x_builder

Ebx:自动刷新机制

自动刷新机制,因为没加泛型,所以无法确定自己内部使用了哪个注入实例,Getx中是在路由里面去回收这些实例的,但是,如果你没使用GetX的路由,又用Obx,你会发现,GetXController居然无法自动回收!!!

此处针对该场景,我会给出一种解决方案

实现

  • 在自动刷新的机制中,需要将基础类型进行封装
    • 主要逻辑在Rx
    • 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),
        ),
      ),
    );
  }
}
  • 效果图

easy_x_ebx

总结

这俩种刷新模式,含金量高的,应该还是自动刷新的机制,思路很有趣,响应式变量和刷新控件通过静态变量的形式建立起联系,cool!又是一种骚操作!

这俩套状态管理机制,我都给出了对依赖注入对象,自动回收的解决方案,希望对大家的思路有所启迪。

最后

终于把最后一篇GetX的原理剖析写完了(只针对GetX状态管理这部分内容),了了一桩心事。。。

  • 有些流程比较绕,特地画了一些图,图文并茂总会让人心情愉悦嘛......

image-20210713163552831

如果大家认真看完了整片文章,可能会发现:状态管理+依赖注入,可以使得使用场景大大的被拓展

  • GetBuilder的自动回收就是借助依赖注入,无缝获取注入实例,从而实现自动回收的操作
  • 而且GetBuilder还无需额外传参数!

整篇文章写下来,我真的尽力了

  • 从InheritedWidget到路由
  • 然后到依赖注入
  • 再就是俩种状态框架原理剖析
  • 最后依据俩种刷新机制,手搓俩套状态管理框架

也算是层层递进的将其中的知识,一点点的展示在大家的面前,希望可以帮到各位!!!

系列文章 + 相关地址

posted @ 2021-07-14 10:03  xdd666  阅读(1145)  评论(2编辑  收藏  举报