【源碼篇】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[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)) {
      //重要:將Ebx中的監聽對象轉換到此處
      easyXNotifier = proxy!.easyXNotifier;
      //變數監聽中刷新
      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到路由
  • 然後到依賴注入
  • 再就是倆種狀態框架原理剖析
  • 最後依據倆種刷新機制,手搓倆套狀態管理框架

也算是層層遞進的將其中的知識,一點點的展示在大家的面前,希望可以幫到各位!!!

系列文章 + 相關地址