【原始碼篇】Flutter GetX深度剖析 | 我們終將走出自己的路(萬字圖文)

小呆呆666發表於2021-07-14

image-20210627221006498

前言

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

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

同時,因為GetX作者的一些言論,也讓一些成見一直伴隨著GetX這個框架。

我寫這篇文章,並不是為GetX正名

  • 我自問自己並不是任何一個狀態框架的死忠者,Provider和Bloc,我寫了相關使用、原理剖析文章和相關程式碼生成外掛
  • 在我心中,這類框架並沒有多麼神祕
  • 因為對其原理較熟,上手使用是一件較為容易的事,所以切換相關框架沒有多大的時間成本
  • 所以,我無需去做一個衛道者

GetX整體設計,有不少優秀點思想,我希望將這些優秀設計思路展現給大家;或許會對你設計自己的框架有一些幫助,同時也是對自己思考歷程的一個記錄。

前置知識

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

InheritedWidget

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

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

InheritedWidget這把神兵藏有什麼?

  • 依賴節點,資料傳輸
  • 定點重新整理機制

資料傳輸

InheritedWidget是我們統稱的一個控制元件名,精髓還是InheritedElement,InheritedWidget的資料傳遞,來看下存和取這倆個過程

存資料

  • InheritedWidget存資料,是一個比較簡單的操作,儲存在InheritedElement中即可
class TransferDataWidget extends InheritedWidget {
  TransferDataWidget({required Widget child}) : super(child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;

  @override
  InheritedElement createElement() => TransferDataElement(this);
}

class TransferDataElement extends InheritedElement {
  TransferDataElement(InheritedWidget widget) : super(widget);

  ///隨便初始化什麼, 設定只讀都行
  String value = '傳輸資料';
}
複製程式碼

取資料

  • 只要是 TransferDataWidget(上面InheritedWidget的子類) 的子節點,通過子節點的BuildContext(Element是BuildContext的實現類),都可以無縫的取資料
var transferDataElement = context.getElementForInheritedWidgetOfExactType<TransferDataWidget>()
            as TransferDataElement?;
var msg = transferDataElement.value;
複製程式碼

可以發現,我們只需要通過Element的getElementForInheritedWidgetOfExactType方法,就可以拿到父節點的TransferDataElement例項(必須繼承InheritedElement)

拿到例項後,自然就可以很簡單的拿到相應資料了

原理

  • 可以發現我們是拿到了XxxInheritedElement例項,繼而拿到了儲存的值,所以關鍵在 getElementForInheritedWidgetOfExactType() 這個方法
    • 程式碼很簡單,就是從 _inheritedWidgets這個map裡取值,泛型T是key
abstract class Element extends DiagnosticableTree implements BuildContext {

    Map<Type, InheritedElement>? _inheritedWidgets;

    @override
    InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
        assert(_debugCheckStateIsActiveForAncestorLookup());
        final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
        return ancestor;
    }
    
    ...
}
複製程式碼
  • 接下來只要搞清楚 _inheritedWidgets 是怎麼存值,那麼一切都會明朗
abstract class ComponentElement extends Element {
    
  @mustCallSuper
  void mount(Element? parent, dynamic newSlot) {
    ...
    _updateInheritance();
  }
    
  void _updateInheritance() {
    assert(_lifecycleState == _ElementLifecycle.active);
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
    
    ...
}

abstract class ProxyElement extends ComponentElement {
    ...
}

class InheritedElement extends ProxyElement {
    InheritedElement(InheritedWidget widget) : super(widget);

    @override
    void _updateInheritance() {
        assert(_lifecycleState == _ElementLifecycle.active);
        final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
        if (incomingWidgets != null)
            _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
        else
            _inheritedWidgets = HashMap<Type, InheritedElement>();
        _inheritedWidgets![widget.runtimeType] = this;
    }
}
複製程式碼

整體上邏輯還是比較清晰

  1. 遇到某個節點為 InheritedWidget 時,會將父節點的 _inheritedWidgets 變數給 incomingWidgets 這個臨時變數
    1. incomingWidgets 為空:為父類Element的 _inheritedWidgets 變數, 例項了一個map物件
    2. incomingWidgets 不為空:將父節點_inheritedWidgets 所有資料深拷貝,返回一個全新的Map例項(含有父節點 _inheritedWidgets 所有資料),賦值給父類Element的 _inheritedWidgets 變數
  2. 將自身例項賦值給父類Element的 _inheritedWidgets 變數,key為其widget的runtimeType

為什麼任何一個Widget的Element例項的 _inheritedWidgets 變數,可直接拿到父節點InheritedElement例項?

  • Element中做了一個父節點向子節點賦值的操作:整個資料傳輸鏈清晰了
abstract class Element extends DiagnosticableTree implements BuildContext {

    Map<Type, InheritedElement>? _inheritedWidgets;

    void _updateInheritance() {
        assert(_lifecycleState == _ElementLifecycle.active);
        _inheritedWidgets = _parent?._inheritedWidgets;
    }

	...
}
複製程式碼
  • 圖示

InheritedWidget存取資料

重新整理機制

InheritedElement和Element之間有一些互動,實際上自帶了一套重新整理機制

  • InheritedElement存子節點Element: _dependents,這個變數是用來儲存,需要重新整理的子Element
class InheritedElement extends ProxyElement {
  InheritedElement(InheritedWidget widget) : super(widget);

  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }

  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }
}
複製程式碼
  • InheritedElement重新整理子Element
    • notifyClients這個方法就是將 _dependents 儲存的Element,全部拿了出來,傳入notifyDependent
    • 在notifyDependent方法中,傳入Element呼叫了自身didChangeDependencies()方法
    • Element的didChangeDependencies() 方法會呼叫 markNeedsBuild() ,來重新整理自身
class InheritedElement extends ProxyElement {
  InheritedElement(InheritedWidget widget) : super(widget);

  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
    
  @override
  void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      ...
      notifyDependent(oldWidget, dependent);
    }
  }
}

abstract class Element extends DiagnosticableTree implements BuildContext {
  ...
    
  @mustCallSuper
  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }
    
  ...
}
複製程式碼

InheritedWidget的子節點是怎麼將自身Element

新增到InheritedElement的_dependents變數裡的呢?

  • Element裡面有個 dependOnInheritedElement 方法
    • Element中dependOnInheritedElement方法,會傳入InheritedElement例項 ancestor
    • ancestor呼叫updateDependencies方法,將自身的Element例項傳入
    • InheritedElement中就將這個Element,新增到_dependents變數中了
abstract class Element extends DiagnosticableTree implements BuildContext {
  ...
    
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
    
  ...
}

class InheritedElement extends ProxyElement {
  InheritedElement(InheritedWidget widget) : super(widget);

  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }

  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }
}
複製程式碼
  • dependOnInheritedElement該方法的使用也很簡單
    • 一般來說在某個Widget使用 getElementForInheritedWidgetOfExactType 獲取父節點的 InheritedElement
    • 然後將其傳入 dependOnInheritedElement 方法中即可
// 舉例
var inheritedElement = context
            .getElementForInheritedWidgetOfExactType<ChangeNotifierEasyP<T>>()
        as EasyPInheritedElement<T>?;
context.dependOnInheritedElement(inheritedElement);
複製程式碼

Provider核心原理,就是採用了InheritedWidget這種重新整理機制

想詳細瞭解Provider相關原理,可參考下面文章

圖示

  • 來看下InheritedWidget重新整理機制的圖示

InheritedWIdget重新整理機制

路由小知識

  • 路由Navigator中基本都是些操作路由的靜態方法,NavigatorState是實現的具體邏輯

路由導圖

大家在使用InheritedWidget獲取資料的時候,或許有過這樣一種困擾:A頁面 ---> B頁面 ---> C頁面

如果我在A頁面使用InheritedWidget儲存了資料,跳轉到B頁面或者C頁面,會發現使用context獲取不到A頁面的InheritedElement

這側面證明了Navigator路由跳轉:A頁面跳轉B頁面,B頁面並不是A頁面的子節點

  • 大致結構:可勉強理解為,Navigator是所有頁面父節點,頁面與頁面之間是平級關係

路由結構

這裡我畫了下大致結構,如有偏差,請務必指出來,我會盡快修改

關於Flutter路由原理解析,可參考此文章(作者為啥現在不更文了呢 ~~)Flutter 路由原理解析

思考

InheritedWidget為我們帶了很多便利

  • 可以在一個Widget樹範圍,獲取到我們想要InheritedElement(通過泛型區分即可)
  • InheritedElement和Element各種互動,也實現了一套極其簡潔的重新整理機制
  • 進行一些深度封裝,甚至可以無縫的管理很多控制元件的資源釋放

但是,Element提供的獲取InheritedElement的方式,終究和路由機制無法很好的結合;這也模組設計無法避免的事情,或許某些模組設計的最優解,很難顧忌到其它模快的一些機制

InheritedWidget這把神兵利器,在我們學習Flutter歷程中給予了很多幫助

  • 但是,當需求逐漸複雜,你的技術不斷精進
  • 屠龍刀這把神兵,或許漸漸的,不太適合你了
  • 有時候,它甚至制約了你的出招
  • 一位製造神兵的鐵匠,在他心中,最好的神兵利器,或許永遠是下一把

大部分的狀態管理框架,將介面層和邏輯層分開,都是邏輯層來處理介面的重新整理;邏輯層可以交給InheritedWidget儲存管理;說明,我們自己也一定可以儲存管理!

  • 自己來管理邏輯層,可以擺脫Element樹的約束,不用被困在Element樹的父子節點中
  • 在路由跳轉的頁面中,可以很輕鬆的獲取上一頁面,下一個頁面或者上上一個頁面邏輯層。

這也是GetX中一個核心思想,這並不是一個多麼新穎或高深技術,但是,我這覺得這是一種思維上的突破,可以帶來更多的可能

依賴注入

說明

依賴注入有如下實現方式(維基百科):

  • 基於介面。實現特定介面以供外部容器注入所依賴型別的物件。
  • 基於 set 方法。實現特定屬性的public set方法,來讓外部容器呼叫傳入所依賴型別的物件。
  • 基於建構函式。實現特定引數的建構函式,在新建物件時傳入所依賴型別的物件。
  • 基於註解。基於Java的註解功能,在私有變數前加“@Autowired”等註解,不需要顯式的定義以上三種程式碼,便可以讓外部容器傳入對應的物件。該方案相當於定義了public的set方法,但是因為沒有真正的set方法,從而不會為了實現依賴注入導致暴露了不該暴露的介面(因為set方法只想讓容器訪問來注入而並不希望其他依賴此類的物件訪問)。

強耦合型別的,基於建構函式

class Test {
  String msg;

  Test(String msg) {
    this.msg = msg;
  }
}
複製程式碼

set方式

class Test {
  String? _msg;

  void setMsg(String msg) {
    this._msg = msg;
  }
}
複製程式碼

如果在Java中,圖一時方便,直接在建構函式裡面傳值,然後需要的值越來越多,導致需要增加該建構函式傳參,因為強耦合很多類,一改建構函式,爆紅一大片(Dart建構函式可選引數的特性,就沒有這類問題了)

  • Getx注入的GetXController都是由GetX框架自己來維護的,如果沒有GetX這個中間層會是什麼樣的?

GetX依賴注入-前

  • 引入GetX這個中間層來管理
    • 看下圖,瞬間就想到了中介者模式
    • 這也是控制反轉的思想(建立物件的控制權本來在自己手上,現在交給了第三方)

GetX依賴注入-後

Put

來看下GetX注入的操作

  • put使用
var controller = Get.put(XxxGetxController());
複製程式碼
  • 看看內部操作
    • 哎,各種騷操作
    • 主要邏輯在Inst中,Inst是GetInterface的擴充套件類
class _GetImpl extends GetInterface {}

final Get = _GetImpl();

extension Inst on GetInterface {
  S put<S>(S dependency,
          {String? tag,
          bool permanent = false,
          InstanceBuilderCallback<S>? builder}) =>
      GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
}
複製程式碼
  • 主要的邏輯看來還是GetInstance中
    • 大家可以看看這地方單例的實現,我發現很多原始碼都用這種方式寫的,非常簡潔
    • 全域性的資料都是存在 _singl 中,這是個Map
      • key:物件的runtimeType或者類的Type + tag
      • value:_InstanceBuilderFactory類,我們傳入dependedt物件會存入這個類中
    • _singl 這個map存值的時候,不是用的put,而是用的putIfAbsent
      • 如果map中有key和傳入key相同的資料,傳入的資料將不會被儲存
      • 也就是說相同類例項的物件,傳入並不會被覆蓋,只會儲存第一條資料,後續被放棄
    • 最後使用find方法,返回傳入的例項
class GetInstance {
  factory GetInstance() => _getInstance ??= GetInstance._();

  const GetInstance._();

  static GetInstance? _getInstance;

  static final Map<String, _InstanceBuilderFactory> _singl = {};

  S put<S>(
    S dependency, {
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

  void _insert<S>({
    bool? isSingleton,
    String? name,
    bool permanent = false,
    required InstanceBuilderCallback<S> builder,
    bool fenix = false,
  }) {
    final key = _getKey(S, name);
    _singl.putIfAbsent(
      key,
      () => _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
      ),
    );
  }
    
  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }
    
  S find<S>({String? tag}) {
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
      if (_singl[key] == null) {
        if (tag == null) {
          throw 'Class "$S" is not registered';
        } else {
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? _singl[key]!.getDependency() as S;
    } else {
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }
}
複製程式碼

find

  • find方法還是蠻簡單的,就是從map中取資料的操作
S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
複製程式碼
  • 看下具體邏輯
    • 先判斷 _singl 中是否含有該key的資料,有則取,無則拋異常
    • 關鍵程式碼: _singl[key]!.getDependency() as S ,直接通過key去map取值就行了
class GetInstance {
  factory GetInstance() => _getInstance ??= GetInstance._();

  const GetInstance._();

  static GetInstance? _getInstance;

  static final Map<String, _InstanceBuilderFactory> _singl = {};
    
  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }
    
  bool isRegistered<S>({String? tag}) => _singl.containsKey(_getKey(S, tag));
    
  S find<S>({String? tag}) {
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
      if (_singl[key] == null) {
        if (tag == null) {
          throw 'Class "$S" is not registered';
        } else {
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? _singl[key]!.getDependency() as S;
    } else {
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }
}
複製程式碼

GetBuilder重新整理機制

使用

為了知識的連續性,此處簡單的寫下使用

  • 邏輯層
class GetCounterEasyLogic extends GetxController {
  var count = 0;

  void increase() {
    ++count;
    update();
  }
}
複製程式碼
  • 介面
class GetCounterEasyPage extends StatelessWidget {
  final GetCounterEasyLogic logic = Get.put(GetCounterEasyLogic());

  @override
  Widget build(BuildContext context) {
    return BaseScaffold(
      appBar: AppBar(title: const Text('計數器-簡單式')),
      body: Center(
        child: GetBuilder<GetCounterEasyLogic>(builder: (logic) {
          return Text(
            '點選了 ${logic.count} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => logic.increase(),
        child: Icon(Icons.add),
      ),
    );
  }
}
複製程式碼

GetBuilder

有一天,我躺在床上思考

  • Obx的狀態管理,GetXController例項回收是放在路由裡面,在很多場景下,存在一些侷限性
  • 後來我想到,GetBuilder使用帶泛型,這就能拿到GetxController例項,GetBuilder又是StatefulWidget
  • 這樣就可以使用它來回收例項,能解決很多場景下,GetXController例項無法回收的問題(不使用Getx路由)
  • 我興致沖沖的開啟Getx專案,準備提PR,然後發現GetBuilder已經在dispose裡面寫了回收例項的操作
  • 淦!

內建回收機制

  • 此處精簡很多程式碼,只展示回收機制的程式碼
class GetBuilder<T extends GetxController> extends StatefulWidget {
  final GetControllerBuilder<T> builder;
  final bool global;
  final String? tag;
  final bool autoRemove;
  final T? init;

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


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

class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  T? controller;
  bool? _isCreator = false;
  VoidCallback? _remove;
  Object? _filter;

  @override
  void initState() {
    super.initState();
    widget.initState?.call(this);

    var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);

    if (widget.global) {
      if (isRegistered) {
        controller = GetInstance().find<T>(tag: widget.tag);
      } else {
        controller = widget.init;
        GetInstance().put<T>(controller!, tag: widget.tag);
      }
    } else {
      controller = widget.init;
      controller?.onStart();
    }

  }

  @override
  void dispose() {
    super.dispose();
    widget.dispose?.call(this);
    if (_isCreator! || widget.assignId) {
      if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
        GetInstance().delete<T>(tag: widget.tag);
      }
    }

    _remove?.call();

    controller = null;
    _isCreator = null;
    _remove = null;
    _filter = null;
  }


  @override
  Widget build(BuildContext context) {
    return widget.builder(controller!);
  }
}
複製程式碼

程式碼裡的邏輯還是相當清晰的,initState獲取例項,dispose回收例項

  1. 通過GetBuilder上泛型獲取相應GetXController例項
    • 不存在:使用init傳入的例項
    • 存在:直接使用;init傳入的例項無效
  2. autoRemove可以控制是否自動回收GetXController例項
    • 預設為true:預設開啟自動回收
    • true:開啟自動回收 false:關閉自動回收

重新整理邏輯

  • 這裡僅保留重新整理邏輯的相關程式碼,去掉了無需關注的程式碼
mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
  void getUpdate() {
    if (mounted) setState(() {});
  }
}

class GetBuilder<T extends GetxController> extends StatefulWidget {
  final GetControllerBuilder<T> builder;
  final bool global;
  final T? init;
  final Object? id;
    
  const GetBuilder({
    Key? key,
    this.init,
    this.id,
    this.global = true,
    required this.builder,
  }) : super(key: key);


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

class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  T? controller;

  @override
  void initState() {
    super.initState();
    ...
     
    if (widget.global) {
      if (isRegistered) {
        controller = GetInstance().find<T>(tag: widget.tag);
      } else {
        controller = widget.init;
        GetInstance().put<T>(controller!, tag: widget.tag);
      }
    } else {
      controller = widget.init;
      controller?.onStart();
    }

    _subscribeToController();
  }

  void _subscribeToController() {
    _remove?.call();
    _remove = (widget.id == null)
        ? controller?.addListener(
            _filter != null ? _filterUpdate : getUpdate,
          )
        : controller?.addListenerId(
            widget.id,
            _filter != null ? _filterUpdate : getUpdate,
          );
  }

  void _filterUpdate() {
    var newFilter = widget.filter!(controller!);
    if (newFilter != _filter) {
      _filter = newFilter;
      getUpdate();
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    widget.didChangeDependencies?.call(this);
  }

  @override
  void didUpdateWidget(GetBuilder oldWidget) {
    super.didUpdateWidget(oldWidget as GetBuilder<T>);
    if (oldWidget.id != widget.id) {
      _subscribeToController();
    }
    widget.didUpdateWidget?.call(oldWidget, this);
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(controller!);
  }
}
複製程式碼

關鍵步驟

  1. 通過泛型獲取注入的GetXController例項
  2. 新增監聽程式碼
    • addListener:新增監聽回撥
    • addListenerId:新增監聽回撥,必須設定id,update重新整理的時候也必須寫上配套的id
  3. 監聽程式碼:核心程式碼就是getUpdate方法,方法在 GetStateUpdaterMixin 中
    • getUpdate()邏輯就是 setState(),重新整理當前GetBuilder

圖示

GetBuilder

Update

  • 觸發邏輯還是很簡單的,使用update即可
    • Ids:和上面的Getbuilder對應起來了,可重新整理對應設定id的GetBuilder
    • condition:是否重新整理一個判斷條件,預設為true(假設必須某個id大於3才能重新整理:update([1, 2, 3, 4], index > 3) )
abstract class GetxController extends DisposableInterface with ListNotifier {
  void update([List<Object>? ids, bool condition = true]) {
    if (!condition) {
      return;
    }
    if (ids == null) {
      refresh();
    } else {
      for (final id in ids) {
        refreshGroup(id);
      }
    }
  }
}
複製程式碼
  • 看下關鍵方法 refresh(),在ListNotifier類中
    • 可以發現,_updaters中泛型就是一個方法
    • 在GetBuilder中新增的監聽就是一個方法引數,方法體裡面就是 setState()
    • 齊活了!GetBuilder新增方法(方法體是setState),update遍歷觸發所有新增方法
typedef GetStateUpdate = void Function();
class ListNotifier implements Listenable {
  List<GetStateUpdate?>? _updaters = <GetStateUpdate?>[];

  HashMap<Object?, List<GetStateUpdate>>? _updatersGroupIds =
      HashMap<Object?, List<GetStateUpdate>>();

  @protected
  void refresh() {
    assert(_debugAssertNotDisposed());
    _notifyUpdate();
  }

  void _notifyUpdate() {
    for (var element in _updaters!) {
      element!();
    }
  }

  ...
}
複製程式碼
  • 如果在update中加了id引數,會走refreshGroup方法,邏輯和refresh幾乎一樣,差別是對id的判斷:有則執行,無則跳過
    • 遍歷所有ids,然後執行refreshGroup方法
abstract class GetxController extends DisposableInterface with ListNotifier {
  void update([List<Object>? ids, bool condition = true]) {
    if (!condition) {
      return;
    }
    if (ids == null) {
      refresh();
    } else {
      for (final id in ids) {
        refreshGroup(id);
      }
    }
  }
}

class ListNotifier implements Listenable {
  HashMap<Object?, List<GetStateUpdate>>? _updatersGroupIds =
      HashMap<Object?, List<GetStateUpdate>>();

  void _notifyIdUpdate(Object id) {
    if (_updatersGroupIds!.containsKey(id)) {
      final listGroup = _updatersGroupIds![id]!;
      for (var item in listGroup) {
        item();
      }
    }
  }

  @protected
  void refreshGroup(Object id) {
    assert(_debugAssertNotDisposed());
    _notifyIdUpdate(id);
  }
}
複製程式碼

總結

  • 來看下GetBuilder重新整理圖示

GetBuilder重新整理機制

Obx重新整理機制

這套重新整理機制,和我們常用的狀態管理框架(provider,bloc)以及上面的GetBuilder,在使用上有一些區別

  • 變數上:基礎型別,實體以及列表之類的資料型別,作者都封裝了一套Rx型別,快捷在資料後加obs

    • 例如:RxString msg = "test".obs(var msg = "test".obs)
  • 更新上:基礎型別直接更新資料就行,實體需要以 .update() 的形式

  • 使用上:使用這類變數,一般要加上 .value ,作者也給出一個快捷方式變數後面加個 ()

    • 我不太推薦加 () 的形式,對後續維護專案人太不友好了

Obx重新整理機制,最有趣應該就是變數改變後,包裹該變數的Obx會自動重新整理!注意喔,僅僅是包裹該變數的Obx會重新整理!其它的Obx並不會重新整理。

這是怎麼做到的呢?

  • 實際上,實現起來很簡單
  • 但是,如果沒有接觸過這個思路,恐怕抓破頭,都很難想出來,還能這麼玩。。。

使用

簡單的來看下使用

  • logic
class GetCounterRxLogic extends GetxController {
  var count = 0.obs;

  ///自增
  void increase() => ++count;
}
複製程式碼
  • view
class GetCounterRxPage extends StatelessWidget {
  final GetCounterRxLogic logic = Get.put(GetCounterRxLogic());

  @override
  Widget build(BuildContext context) {
    return BaseScaffold(
      appBar: AppBar(title: const Text('計數器-響應式')),
      body: Center(
        child: Obx(() {
          return Text(
            '點選了 ${logic.count.value} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => logic.increase(),
        child: Icon(Icons.add),
      ),
    );
  }
}
複製程式碼

Rx類變數

此處以 RxInt 為例,來看下其內部實現

  • 先來看下整型後面的擴充 obs ,這是一個擴充套件類,0.obs 等同 RxInt(0)
extension IntExtension on int {
  /// Returns a `RxInt` with [this] `int` as initial value.
  RxInt get obs => RxInt(this);
}
複製程式碼
  • 來看下RxInt:這地方明確了使用 .value 執行時,會自動返回一個當前例項,並修改相應value數值
class RxInt extends Rx<int> {
  RxInt(int initial) : super(initial);

  /// Addition operator.
  RxInt operator +(int other) {
    value = value + other;
    return this;
  }

  /// Subtraction operator.
  RxInt operator -(int other) {
    value = value - other;
    return this;
  }
}
複製程式碼
  • 來看下父類 Rx
    • 這地方出現了一個很重要的類: _RxImpl
class Rx<T> extends _RxImpl<T> {
  Rx(T initial) : super(initial);

  @override
  dynamic toJson() {
    try {
      return (value as dynamic)?.toJson();
    } on Exception catch (_) {
      throw '$T has not method [toJson]';
    }
  }
}
複製程式碼
  • _RxImpl 類繼承了 RxNotifier 和 with 了 RxObjectMixin
  • 這個類內容是比較龐大的,主要是 RxNotifier 和 RxObjectMixin 內容很多
  • 程式碼很多,先展示下完整程式碼;在下一個說明處會進行簡化
abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
  _RxImpl(T initial) {
    _value = initial;
  }

  void addError(Object error, [StackTrace? stackTrace]) {
    subject.addError(error, stackTrace);
  }

  Stream<R> map<R>(R mapper(T? data)) => stream.map(mapper);

  void update(void fn(T? val)) {
    fn(_value);
    subject.add(_value);
  }

  void trigger(T v) {
    var firstRebuild = this.firstRebuild;
    value = v;
    if (!firstRebuild) {
      subject.add(v);
    }
  }
}

class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;

mixin NotifyManager<T> {
  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};

  bool get canUpdate => _subscriptions.isNotEmpty;

  void addListener(GetStream<T> rxGetx) {
    if (!_subscriptions.containsKey(rxGetx)) {
      final subs = rxGetx.listen((data) {
        if (!subject.isClosed) subject.add(data);
      });
      final listSubscriptions =
          _subscriptions[rxGetx] ??= <StreamSubscription>[];
      listSubscriptions.add(subs);
    }
  }

  StreamSubscription<T> listen(
    void Function(T) onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) =>
      subject.listen(
        onData,
        onError: onError,
        onDone: onDone,
        cancelOnError: cancelOnError ?? false,
      );

  void close() {
    _subscriptions.forEach((getStream, _subscriptions) {
      for (final subscription in _subscriptions) {
        subscription.cancel();
      }
    });

    _subscriptions.clear();
    subject.close();
  }
}

mixin RxObjectMixin<T> on NotifyManager<T> {
  late T _value;

  void refresh() {
    subject.add(value);
  }

  T call([T? v]) {
    if (v != null) {
      value = v;
    }
    return value;
  }

  bool firstRebuild = true;

  String get string => value.toString();

  @override
  String toString() => value.toString();

  dynamic toJson() => value;

  @override
  bool operator ==(dynamic o) {
    if (o is T) return value == o;
    if (o is RxObjectMixin<T>) return value == o.value;
    return false;
  }

  @override
  int get hashCode => _value.hashCode;

  set value(T val) {
    if (subject.isClosed) return;
    if (_value == val && !firstRebuild) return;
    firstRebuild = false;
    _value = val;

    subject.add(_value);
  }

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy!.addListener(subject);
    }
    return _value;
  }

  Stream<T?> get stream => subject.stream;

  void bindStream(Stream<T> stream) {
    final listSubscriptions =
        _subscriptions[subject] ??= <StreamSubscription>[];
    listSubscriptions.add(stream.listen((va) => value = va));
  }
}
複製程式碼
  • 簡化 _RxImpl,上面內容太多了,我這地方簡化下,把需要關注的內容展示出來:此處有幾個需要重點關注的點
    • RxInt是一個內建callback的資料型別(GetStream)
    • RxInt的value變數改變的時候(set value),會觸發subject.add(_value),內部邏輯是自動重新整理操作
    • 獲取RxInt的value變數的時候(get value),會有一個新增監聽的操作,這個灰常重要!
abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {

  void update(void fn(T? val)) {
    fn(_value);
    subject.add(_value);
  }
}

class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;

mixin NotifyManager<T> {
  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};

  bool get canUpdate => _subscriptions.isNotEmpty;

  void addListener(GetStream<T> rxGetx) {
    if (!_subscriptions.containsKey(rxGetx)) {
      final subs = rxGetx.listen((data) {
        if (!subject.isClosed) subject.add(data);
      });
      final listSubscriptions =
          _subscriptions[rxGetx] ??= <StreamSubscription>[];
      listSubscriptions.add(subs);
    }
  }
}

mixin RxObjectMixin<T> on NotifyManager<T> {
  late T _value;

  void refresh() {
    subject.add(value);
  }

  set value(T val) {
    if (subject.isClosed) return;
    if (_value == val && !firstRebuild) return;
    firstRebuild = false;
    _value = val;

    subject.add(_value);
  }

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy!.addListener(subject);
    }
    return _value;
  }
}
複製程式碼
  • 為啥GetStream的add會有重新整理操作:刪了很多程式碼,保留了重點程式碼
    • 呼叫add方法時候,會呼叫 _notifyData 方法
    • _notifyData 方法中,會遍歷 _onData 列表,根據條件會執行其泛型的 _data 的方法
    • 我猜,_data 中的方法體,十有八九在某個地方肯定新增了 setState()
class GetStream<T> {
  GetStream({this.onListen, this.onPause, this.onResume, this.onCancel});
  List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];

  FutureOr<void> addSubscription(LightSubscription<T> subs) async {
    if (!_isBusy!) {
      return _onData!.add(subs);
    } else {
      await Future.delayed(Duration.zero);
      return _onData!.add(subs);
    }
  }

  void _notifyData(T data) {
    _isBusy = true;
    for (final item in _onData!) {
      if (!item.isPaused) {
        item._data?.call(data);
      }
    }
    _isBusy = false;
  }

  T? _value;

  T? get value => _value;

  void add(T event) {
    assert(!isClosed, 'You cannot add event to closed Stream');
    _value = event;
    _notifyData(event);
  }
}

typedef OnData<T> = void Function(T data);

class LightSubscription<T> extends StreamSubscription<T> {
  OnData<T>? _data;
}
複製程式碼
  • 圖示,先來看下,Rx類具有的功能
    • get value 新增監聽
    • set value 執行已新增的監聽

Rx類變數

Obx重新整理機制

Obx最大的特殊之處,應該就是使用它的時候,不需要加泛型且能自動重新整理,這是怎麼做到的呢?

  • Obx:程式碼並不多,但是皆有妙用
    • Obx繼承ObxWidget,ObxWidget實際上也是一個StatefulWidget
    • _ObxState 中程式碼就是核心程式碼了
class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}


abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key? key}) : super(key: key);

  @override
  _ObxState createState() => _ObxState();

  @protected
  Widget build();
}

class _ObxState extends State<ObxWidget> {
  RxInterface? _observer;
  late StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

  @override
  void initState() {
    subs = _observer!.listen(_updateTree, cancelOnError: false);
    super.initState();
  }

  void _updateTree(_) {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    subs.cancel();
    _observer!.close();
    super.dispose();
  }

  Widget get notifyChilds {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.build();
    if (!_observer!.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}
複製程式碼

新增監聽

一個控制元件想重新整理,肯定有新增監聽的邏輯,再在某個地方手動觸發

  • 看下_ObxState類在哪新增監聽:只展示監聽新增的程式碼

  • _ObxState初始化的時候,會例項化一個 RxNotifier() 物件,使用 _observer變數接受:這個操作很重要

  • initState中做了一個比較關鍵的操作,_observer的listener方法中,將 _updateTree方法傳進去了,這個方法中的邏輯體就是 setState()

class _ObxState extends State<ObxWidget> {
  RxInterface? _observer;
  late StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

  @override
  void initState() {
    subs = _observer!.listen(_updateTree, cancelOnError: false);
    super.initState();
  }

  void _updateTree(_) {
    if (mounted) {
      setState(() {});
    }
  }
}
複製程式碼

上述很多邏輯和 RxNotifier 類相關,來看下這個類

  • RxNotifier 這個類,內部會例項一個 GetStream() 物件,然後賦值給 subject
  • 上面賦值 _updateTree 方法被傳入的 GetStream() 類中,最終新增 _onData 該列表變數中
  • 瞟一眼 _notifyData方法,是不是遍歷執行了 _onData 列表中item的方法( item. _data?.call(data) )
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;

mixin NotifyManager<T> {
  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};

  bool get canUpdate => _subscriptions.isNotEmpty;

  StreamSubscription<T> listen(
    void Function(T) onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) =>
      subject.listen(
        onData,
        onError: onError,
        onDone: onDone,
        cancelOnError: cancelOnError ?? false,
      );
}

class GetStream<T> {
  void Function()? onListen;
  void Function()? onPause;
  void Function()? onResume;
  FutureOr<void> Function()? onCancel;

  GetStream({this.onListen, this.onPause, this.onResume, this.onCancel});
  List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];

  FutureOr<void> addSubscription(LightSubscription<T> subs) async {
    if (!_isBusy!) {
      return _onData!.add(subs);
    } else {
      await Future.delayed(Duration.zero);
      return _onData!.add(subs);
    }
  }

  int? get length => _onData?.length;

  bool get hasListeners => _onData!.isNotEmpty;
    
  void _notifyData(T data) {
    _isBusy = true;
    for (final item in _onData!) {
      if (!item.isPaused) {
        item._data?.call(data);
      }
    }
    _isBusy = false;
  }

  LightSubscription<T> listen(void Function(T event) onData,
      {Function? onError, void Function()? onDone, bool? cancelOnError}) {
    final subs = LightSubscription<T>(
      removeSubscription,
      onPause: onPause,
      onResume: onResume,
      onCancel: onCancel,
    )
      ..onData(onData)
      ..onError(onError)
      ..onDone(onDone)
      ..cancelOnError = cancelOnError;
    addSubscription(subs);
    onListen?.call();
    return subs;
  }
}
複製程式碼
  • 上面程式碼流程有一點繞,下面畫了一個圖,希望對各位有所幫助

Obx監聽新增

監聽轉移

在_ObxState類中做了一個很重要,監聽物件轉移的操作

_observer中的物件已經拿到了Obx控制元件內部的setState方法,現在需要將它轉移出去啦!

  • 下面貼下將 _observer 中物件轉移出去的程式碼:主要的邏輯就是在 notifyChilds 方法中
    • RxInterface 類中有個 proxy 靜態變數,這個變數十分重要,他是一箇中轉變數!
`class _ObxState extends State<ObxWidget> {
  RxInterface? _observer;

  _ObxState() {
    _observer = RxNotifier();
  }

  Widget get notifyChilds {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.build();
    if (!_observer!.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}

abstract class RxInterface<T> {
  bool get canUpdate;

  void addListener(GetStream<T> rxGetx);

  void close();

  static RxInterface? proxy;

  StreamSubscription<T> listen(void Function(T event) onData,
      {Function? onError, void Function()? onDone, bool? cancelOnError});
}
複製程式碼

notifyChilds中的幾行程式碼都有深意,一行行的解讀下

  • final observer = RxInterface.proxy:RxInterface.proxy正常情況為空,但是,可能作為中間變數暫存物件的情況,現在暫時將他的物件取出來,存在observer變數中

  • RxInterface.proxy = _observer:將我們在 _ObxState類中例項化的 RxNotifier() 物件的地址,賦值給了RxInterface.proxy

    • 注意:這裡,RxInterface.proxy中 RxNotifier() 例項,有當前Obx控制元件的setState() 方法
  • final result = widget.build():這個賦值相當重要了!呼叫我們在外部傳進的Widget

    • 如果這個Widget中有響應式變數,那麼一定會呼叫該變數中獲取 get value

    • 還記得get value的程式碼嗎?

      mixin RxObjectMixin<T> on NotifyManager<T> {
        late T _value;
          
        T get value {
          if (RxInterface.proxy != null) {
            RxInterface.proxy!.addListener(subject);
          }
          return _value;
        }
      }
      
      mixin NotifyManager<T> {
        GetStream<T> subject = GetStream<T>();
      }
      複製程式碼
    • 終於建立起聯絡了,將變數中 GetStream 例項,新增到了Obx中的 RxNotifier() 例項;RxNotifier() 例項中有一個 subject(GetStream ) 例項,Rx型別中資料變化會觸發 subject 變化,最終重新整理Obx

      mixin NotifyManager<T> {
        GetStream<T> subject = GetStream<T>();
        final _subscriptions = <GetStream, List<StreamSubscription>>{};
      
        bool get canUpdate => _subscriptions.isNotEmpty;
          
        void addListener(GetStream<T> rxGetx) {
          if (!_subscriptions.containsKey(rxGetx)) {
            //重點 GetStream中listen方法是用來新增監聽方法的,add的時候會重新整理監聽方法
            final subs = rxGetx.listen((data) {
              if (!subject.isClosed) subject.add(data);
            });
            final listSubscriptions =
                _subscriptions[rxGetx] ??= <StreamSubscription>[];
            listSubscriptions.add(subs);
          }
        }
      }
      複製程式碼
  • if (!_observer!.canUpdate) {}:這個判斷就很簡單了,如果我們傳入的Widget中沒有Rx型別變數, _subscriptions陣列就會為空,這個判斷就會過不了

  • RxInterface.proxy = observer:將RxInterface.proxy中原來的值,重新賦給自己,至此 _ObxState 中的 _observer物件地址,進行了一番奇幻旅遊後,結束了自己的使命

圖示

Obx監聽轉移

總結

Obx的重新整理機制,還是蠻有有趣的

  • Rx變數改變,自動重新整理包裹其變數Obx控制元件,其它的Obx控制元件並不會重新整理
  • 使用Obx控制元件,不需要寫泛型!牛批!

但是,我認為Obx重新整理機制,也是有著自身的缺陷的,從其實現原理上看,這是無法避免的

  • 因為Obx的自動重新整理,必須需要每一個變數都自帶監聽觸發機制;所以,所有的基礎型別,實體以及列表,都需要重新封裝,這會造成很嚴重的使用影響:變數的賦值,型別標定,重新整理都很正常寫法有差異,不熟悉該寫法的人,看了後,會很難受
  • 因為對所有型別重新封裝,經過上面的程式碼回溯,大家也發現,封裝型別的程式碼相當多;封裝型別佔用資源肯定要比dart自帶型別的大(這個問題可以避免:封裝一個響應式的變數,並不一定需要很多程式碼,下面我給出了一個封裝參考)

手搓一個狀態管理框架

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

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

依賴注入

  • 在做重新整理機制前,首先必須寫一個依賴注入的類,我們需要自己管理邏輯層的那些例項
    • 我這邊寫了一個極其簡單,僅實現三種基礎功能:注入,獲取,刪除
///依賴注入,外部可將例項,注入該類中,由該類管理
class Easy {
  ///注入例項
  static T put<T>(T dependency, {String? tag}) =>
      _EasyInstance().put(dependency, tag: tag);

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

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

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

  static _EasyInstance? _instance;

  _EasyInstance._();

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

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

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

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

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

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

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

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

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

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

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

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

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

    for (final entry in _listeners) {
      try {
        entry.call();
      } catch (e) {
        print(e.toString());
      }
    }
  }
}
複製程式碼

EasyBuilder

實現

  • 該模式需要自定義一個基類
    • 我這地方寫的極簡,相關生命週期都沒加,這個加起來也很容易,定義各個生命週期,在Builder控制元件裡面觸發,就可以了
    • 為了程式碼簡潔,這個暫且不表
class EasyXController {
  EasyXNotifier xNotifier = EasyXNotifier();

  ///重新整理控制元件
  void update() {
    xNotifier.notify();
  }
}
複製程式碼
  • 再來看看最核心的EasyBuilder控制元件:這就搞定了!
    • 實現程式碼寫的極其簡單,希望大家思路能有所明晰
///重新整理控制元件,自帶回收機制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
  final Widget Function(T logic) builder;

  final String? tag;
  final bool autoRemove;

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

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

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

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

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

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

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(controller);
  }
}
複製程式碼

使用

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

  void increase() {
    ++count;
    update();
  }
}
複製程式碼
  • 介面層
class EasyXCounterPage extends StatelessWidget {
  final EasyXCounterLogic logic = Easy.put(EasyXCounterLogic());

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

easy_x_builder

Ebx:自動重新整理機制

自動重新整理機制,因為沒加泛型,所以無法確定自己內部使用了哪個注入例項,Getx中是在路由裡面去回收這些例項的,但是,如果你沒使用GetX的路由,又用Obx,你會發現,GetXController居然無法自動回收!!!

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

實現

  • 在自動重新整理的機制中,需要將基礎型別進行封裝
    • 主要邏輯在Rx中
    • set value 和 get value是關鍵
///擴充函式
extension IntExtension on int {
  RxInt get ebs => RxInt(this);
}

extension StringExtension on String {
  RxString get ebs => RxString(this);
}

extension DoubleExtension on double {
  RxDouble get ebs => RxDouble(this);
}

extension BoolExtension on bool {
  RxBool get ebs => RxBool(this);
}

///封裝各型別
class RxInt extends Rx<int> {
  RxInt(int initial) : super(initial);

  RxInt operator +(int other) {
    value = value + other;
    return this;
  }

  RxInt operator -(int other) {
    value = value - other;
    return this;
  }
}

class RxDouble extends Rx<double> {
  RxDouble(double initial) : super(initial);

  RxDouble operator +(double other) {
    value = value + other;
    return this;
  }

  RxDouble operator -(double other) {
    value = value - other;
    return this;
  }
}

class RxString extends Rx<String> {
  RxString(String initial) : super(initial);
}

class RxBool extends Rx<bool> {
  RxBool(bool initial) : super(initial);
}

///主體邏輯
class Rx<T> {
  EasyXNotifier subject = EasyXNotifier();

  Rx(T initial) {
    _value = initial;
  }

  late T _value;

  bool firstRebuild = true;

  String get string => value.toString();

  @override
  String toString() => value.toString();

  set value(T val) {
    if (_value == val && !firstRebuild) return;
    firstRebuild = false;
    _value = val;

    subject.notify();
  }

  T get value {
    if (RxEasy.proxy != null) {
      RxEasy.proxy!.addListener(subject);
    }
    return _value;
  }
}
複製程式碼
  • 需要寫一個非常重要的中轉類,這個也會儲存響應式變數的監聽物件
    • 這個類有著非常核心的邏輯:他將響應式變數和重新整理控制元件關聯起來了!
class RxEasy {
  EasyXNotifier easyXNotifier = EasyXNotifier();

  Map<EasyXNotifier, String> _listenerMap = {};

  bool get canUpdate => _listenerMap.isNotEmpty;

  static RxEasy? proxy;

  void addListener(EasyXNotifier notifier) {
    if (!_listenerMap.containsKey(notifier)) {
      //變數監聽中重新整理
      notifier.addListener(() {
        //重新整理ebx中新增的監聽
        easyXNotifier.notify();
      });
      //新增進入map中
      _listenerMap[notifier] = '';
    }
  }
}
複製程式碼
  • 重新整理控制元件Ebx
typedef WidgetCallback = Widget Function();

class Ebx extends StatefulWidget {
  const Ebx(this.builder, {Key? key}) : super(key: key);

  final WidgetCallback builder;

  @override
  _EbxState createState() => _EbxState();
}

class _EbxState extends State<Ebx> {
  RxEasy _rxEasy = RxEasy();

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

    _rxEasy.easyXNotifier.addListener(() {
      if (mounted) setState(() {});
    });
  }

  Widget get notifyChild {
    final observer = RxEasy.proxy;
    RxEasy.proxy = _rxEasy;
    final result = widget.builder();
    if (!_rxEasy.canUpdate) {
      throw 'Widget lacks Rx type variables';
    }
    RxEasy.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) {
    return notifyChild;
  }

  @override
  void dispose() {
    _rxEasy.easyXNotifier.dispose();

    super.dispose();
  }
}
複製程式碼
  • 在上面說了,在自動重新整理機制中,自動回收依賴例項是個蛋筒的問題,此處我寫了一個回收控制元件,可以解決此問題
    • 使用時,必須套一層了;如果大家有更好的思路,麻煩在評論裡告知
class EasyBindWidget extends StatefulWidget {
  const EasyBindWidget({
    Key? key,
    this.bind,
    this.tag,
    this.binds,
    this.tags,
    required this.child,
  })  : assert(
          binds == null || tags == null || binds.length == tags.length,
          'The binds and tags arrays length should be equal\n'
          'and the elements in the two arrays correspond one-to-one',
        ),
        super(key: key);

  final Object? bind;
  final String? tag;

  final List<Object>? binds;
  final List<String>? tags;

  final Widget child;

  @override
  _EasyBindWidgetState createState() => _EasyBindWidgetState();
}

class _EasyBindWidgetState extends State<EasyBindWidget> {
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  @override
  void dispose() {
    _closeController();
    _closeControllers();

    super.dispose();
  }

  void _closeController() {
    if (widget.bind == null) {
      return;
    }

    var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
    Easy.delete(key: key);
  }

  void _closeControllers() {
    if (widget.binds == null) {
      return;
    }

    for (var i = 0; i < widget.binds!.length; i++) {
      var type = widget.binds![i].runtimeType.toString();

      if (widget.tags == null) {
        Easy.delete(key: type);
      } else {
        var key = type + (widget.tags?[i] ?? '');
        Easy.delete(key: key);
      }
    }
  }
}
複製程式碼

使用

  • 邏輯層,這次,我們們連基類都不需要寫
class EasyXEbxCounterLogic {
  RxInt count = 0.ebs;

  ///自增
  void increase() => ++count;
}
複製程式碼
  • 介面層:頁面頂節點套了一個EasyBindWidget,可以保證依賴注入例項可以自動回收
class EasyXEbxCounterPage extends StatelessWidget {
  final EasyXEbxCounterLogic logic = Easy.put(EasyXEbxCounterLogic());

  @override
  Widget build(BuildContext context) {
    return EasyBindWidget(
      bind: logic,
      child: BaseScaffold(
        appBar: AppBar(title: const Text('EasyX-自定義Ebx重新整理機制')),
        body: Center(
          child: Ebx(() {
            return Text(
              '點選了 ${logic.count.value} 次',
              style: TextStyle(fontSize: 30.0),
            );
          }),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => logic.increase(),
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}
複製程式碼
  • 效果圖

easy_x_ebx

總結

這倆種重新整理模式,含金量高的,應該還是自動重新整理的機制,思路很有趣,響應式變數和重新整理控制元件通過靜態變數的形式建立起聯絡,cool!又是一種騷操作!

這倆套狀態管理機制,我都給出了對依賴注入物件,自動回收的解決方案,希望對大家的思路有所啟迪。

最後

終於把最後一篇GetX的原理剖析寫完了(只針對GetX狀態管理這部分內容),了了一樁心事。。。

  • 有些流程比較繞,特地畫了一些圖,圖文並茂總會讓人心情愉悅嘛......

image-20210713163552831

如果大家認真看完了整片文章,可能會發現:狀態管理+依賴注入,可以使得使用場景大大的被擴充

  • GetBuilder的自動回收就是藉助依賴注入,無縫獲取注入例項,從而實現自動回收的操作
  • 而且GetBuilder還無需額外傳引數!

整篇文章寫下來,我真的盡力了

  • 從InheritedWidget到路由
  • 然後到依賴注入
  • 再就是倆種狀態框架原理剖析
  • 最後依據倆種重新整理機制,手搓倆套狀態管理框架

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

系列文章 + 相關地址

相關文章