【源碼篇】Flutter GetX深度剖析 | 咱們終將走出本身的路(萬字圖文)

image-20210627221006498

前言

人心中的成見是一座大山,任你怎麼努力都休想搬動。

這是電影《哪吒》裏申公豹說的一句話,也是貫徹整部電影的一個主題;或許這句話引發了太多人的共鳴:35歲職場危機,大廠卡本科學歷,無房無車結婚難等等,因此,這句話也常常被人提起。git

同時,由於GetX做者的一些言論,也讓一些成見一直伴隨着GetX這個框架。github

我寫這篇文章,並非爲GetX正名segmentfault

  • 我自問本身並非任何一個狀態框架的死忠者,Provider和Bloc,我寫了相關使用、原理剖析文章和相關代碼生成插件
  • 在我心中,這類框架並無多麼神祕
  • 由於對其原理較熟,上手使用是一件較爲容易的事,因此切換相關框架沒有多大的時間成本
  • 因此,我無需去作一個衛道者

GetX總體設計,有很多優秀點思想,我但願將這些優秀設計思路展示給你們;或許會對你設計本身的框架有一些幫助,同時也是對本身思考歷程的一個記錄。數組

前置知識

在說GetX設計思想以前,須要先介紹幾個知識,在Flutter茁壯發展的歷程裏,他們都留下了濃墨重彩的一筆

InheritedWidget

不得不說,這個控件真的是一個神奇控件,它就彷彿是一把神兵利器app

  • 寶刀屠龍,號令天下,莫敢不從,倚天不出,誰與爭鋒
  • 倚天劍,劍藏《九陰真經》
  • 屠龍刀,刀藏《降龍十八掌》、《武穆遺書》

InheritedWidget這把神兵藏有什麼?框架

  • 依賴節點,數據傳輸
  • 定點刷新機制

數據傳輸

InheritedWidget是咱們統稱的一個控件名,精髓仍是InheritedElement,InheritedWidget的數據傳遞,來看下存和取這倆個過程less

存數據
  • 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)async

拿到實例後,天然就能夠很簡單的拿到相應數據了ide

原理
  • 能夠發現咱們是拿到了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的數據,有則取,無則拋異常
    • 關鍵代碼: _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<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的代碼嗎?

      mixin RxObjectMixin<T> on NotifyManager<T> {
        late T _value;
          
        T get value {
          if (RxInterface.proxy != null) {
            RxInterface.proxy!.addListener(subject);
          }
          return _value;
        }
      }
      
      mixin NotifyManager<T> {
        GetStream<T> subject = GetStream<T>();
      }
    • 終於創建起聯繫了,將變量中 GetStream 實例,添加到了Obx中的 RxNotifier() 實例;RxNotifier() 實例中有一個 subject(GetStream ) 實例,Rx類型中數據變化會觸發 subject 變化,最終刷新Obx

      mixin NotifyManager<T> {
        GetStream<T> subject = GetStream<T>();
        final _subscriptions = <GetStream, List<StreamSubscription>>{};
      
        bool get canUpdate => _subscriptions.isNotEmpty;
          
        void addListener(GetStream<T> rxGetx) {
          if (!_subscriptions.containsKey(rxGetx)) {
            //重點 GetStream中listen方法是用來添加監聽方法的,add的時候會刷新監聽方法
            final subs = rxGetx.listen((data) {
              if (!subject.isClosed) subject.add(data);
            });
            final listSubscriptions =
                _subscriptions[rxGetx] ??= <StreamSubscription>[];
            listSubscriptions.add(subs);
          }
        }
      }
  • if (!_observer!.canUpdate) {}:這個判斷就很簡單了,若是咱們傳入的Widget中沒有Rx類型變量, _subscriptions數組就會爲空,這個判斷就會過不了
  • RxInterface.proxy = observer:將RxInterface.proxy中原來的值,從新賦給本身,至此 _ObxState 中的 _observer對象地址,進行了一番奇幻旅遊後,結束了本身的使命
圖示

Obx監聽轉移

總結

Obx的刷新機制,仍是蠻有有趣的

  • Rx變量改變,自動刷新包裹其變量Obx控件,其它的Obx控件並不會刷新
  • 使用Obx控件,不須要寫泛型!牛批!

可是,我認爲Obx刷新機制,也是有着自身的缺陷的,從其實現原理上看,這是沒法避免的

  • 由於Obx的自動刷新,必須須要每個變量都自帶監聽觸發機制;因此,全部的基礎類型,實體以及列表,都須要從新封裝,這會形成很嚴重的使用影響:變量的賦值,類型標定,刷新都很正常寫法有差別,不熟悉該寫法的人,看了後,會很難受
  • 由於對全部類型從新封裝,通過上面的代碼回溯,你們也發現,封裝類型的代碼至關多;封裝類型佔用資源確定要比dart自帶類型的大(這個問題能夠避免:封裝一個響應式的變量,並不必定須要不少代碼,下面我給出了一個封裝參考)

手搓一個狀態管理框架

GetX內置了倆套狀態管理機制,這邊也會按照其刷新機制,手搓倆套出來

我會用極其簡單的代碼,再現倆套經典的機制

依賴注入

  • 在作刷新機制前,首先必須寫一個依賴注入的類,咱們須要本身管理邏輯層的那些實例

    • 我這邊寫了一個極其簡單,僅實現三種基礎功能:注入,獲取,刪除
///依賴注入,外部可將實例,注入該類中,由該類管理
class Easy {
  ///注入實例
  static T put<T>(T dependency, {String? tag}) =>
      _EasyInstance().put(dependency, tag: tag);

  ///獲取注入的實例
  static T find<T>({String? tag, String? key}) =>
      _EasyInstance().find<T>(tag: tag, key: key);

  ///刪除實例
  static bool delete<T>({String? tag, String? key}) =>
      _EasyInstance().delete<T>(tag: tag, key: key);
}

///具體邏輯
class _EasyInstance {
  factory _EasyInstance() => _instance ??= _EasyInstance._();

  static _EasyInstance? _instance;

  _EasyInstance._();

  static final Map<String, _InstanceInfo> _single = {};

  ///注入實例
  T put<T>(T dependency, {String? tag}) {
    final key = _getKey(T, tag);
    //只保存第一次注入:針對自動刷新機制優化,每次熱重載的時候,數據不會重置
    _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
    return find<T>(tag: tag);
  }

  ///獲取注入的實例
  T find<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    var info = _single[newKey];

    if (info?.value != null) {
      return info!.value;
    } else {
      throw '"$T" not found. You need to call "Easy.put($T())""';
    }
  }

  ///刪除實例
  bool delete<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    if (!_single.containsKey(newKey)) {
      print('Instance "$newKey" already removed.');
      return false;
    }

    _single.remove(newKey);
    print('Instance "$newKey" deleted.');
    return true;
  }

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }
}

class _InstanceInfo<T> {
  _InstanceInfo(this.value);

  T value;
}
  • 自定義一個監聽類,這個類很重要,下面倆種機制都須要用到
///自定義個監聽觸發類
class EasyXNotifier {
  List<VoidCallback> _listeners = [];

  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  void removeListener(VoidCallback listener) {
    for (final entry in _listeners) {
      if (entry == listener) {
        _listeners.remove(entry);
        return;
      }
    }
  }

  void dispose() {
    _listeners.clear();
  }

  void notify() {
    if (_listeners.isEmpty) return;

    for (final entry in _listeners) {
      try {
        entry.call();
      } catch (e) {
        print(e.toString());
      }
    }
  }
}

EasyBuilder

實現

  • 該模式須要自定義一個基類

    • 我這地方寫的極簡,相關生命週期都沒加,這個加起來也很容易,定義各個生命週期,在Builder控件裏面觸發,就能夠了
    • 爲了代碼簡潔,這個暫且不表
class EasyXController {
  EasyXNotifier xNotifier = EasyXNotifier();

  ///刷新控件
  void update() {
    xNotifier.notify();
  }
}
  • 再來看看最核心的EasyBuilder控件:這就搞定了!

    • 實現代碼寫的極其簡單,但願你們思路能有所明晰
///刷新控件,自帶回收機制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
  final Widget Function(T logic) builder;

  final String? tag;
  final bool autoRemove;

  const EasyBuilder({
    Key? key,
    required this.builder,
    this.autoRemove = true,
    this.tag,
  }) : super(key: key);

  @override
  _EasyBuilderState<T> createState() => _EasyBuilderState<T>();
}

class _EasyBuilderState<T extends EasyXController>
    extends State<EasyBuilder<T>> {
  late T controller;

  @override
  void initState() {
    super.initState();

    controller = Easy.find<T>(tag: widget.tag);
    controller.xNotifier.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    if (widget.autoRemove) {
      Easy.delete<T>(tag: widget.tag);
    }
    controller.xNotifier.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(controller);
  }
}

使用

  • 使用很簡單,先看下邏輯層
class EasyXCounterLogic extends EasyXController {
  var count = 0;

  void increase() {
    ++count;
    update();
  }
}
  • 界面層
class EasyXCounterPage extends StatelessWidget {
  final EasyXCounterLogic logic = Easy.put(EasyXCounterLogic());

  @override
  Widget build(BuildContext context) {
    return BaseScaffold(
      appBar: AppBar(title: const Text('EasyX-自定義EasyBuilder刷新機制')),
      body: Center(
        child: EasyBuilder<EasyXCounterLogic>(builder: (logic) {
          return Text(
            '點擊了 ${logic.count} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => logic.increase(),
        child: Icon(Icons.add),
      ),
    );
  }
}
  • 效果圖

easy_x_builder

Ebx:自動刷新機制

自動刷新機制,由於沒加泛型,因此沒法肯定本身內部使用了哪一個注入實例,Getx中是在路由裏面去回收這些實例的,可是,若是你沒使用GetX的路由,又用Obx,你會發現,GetXController竟然沒法自動回收!!!

此處針對該場景,我會給出一種解決方案

實現

  • 在自動刷新的機制中,須要將基礎類型進行封裝

    • 主要邏輯在Rx<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到路由
  • 而後到依賴注入
  • 再就是倆種狀態框架原理剖析
  • 最後依據倆種刷新機制,手搓倆套狀態管理框架

也算是層層遞進的將其中的知識,一點點的展現在你們的面前,但願能夠幫到各位!!!

系列文章 + 相關地址
相關文章
相關標籤/搜索