【源码篇】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<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;
  • }
  • }
展开

整体上逻辑还是比较清晰

  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的数据,有则取,无则抛异常
    • 关键代码: _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回收实例

  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<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 执行已添加的监听
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<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;
  • }
  • }
展开
  • 上面代码流程有一点绕,下面画了一个图,希望对各位有所帮助
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的代码吗?
  • ```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监听转移

总结

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

总结

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

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

最后

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

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

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

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

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

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

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

系列文章 + 相关地址

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>