人心中的成見是一座大山,任你怎麼努力都休想搬動。
這是電影《哪吒》裏申公豹說的一句話,也是貫徹整部電影的一個主題;或許這句話引發了太多人的共鳴:35歲職場危機,大廠卡本科學歷,無房無車結婚難等等,因此,這句話也常常被人提起。git
同時,由於GetX做者的一些言論,也讓一些成見一直伴隨着GetX這個框架。github
我寫這篇文章,並非爲GetX正名segmentfault
GetX總體設計,有很多優秀點思想,我但願將這些優秀設計思路展示給你們;或許會對你設計本身的框架有一些幫助,同時也是對本身思考歷程的一個記錄。數組
在說GetX設計思想以前,須要先介紹幾個知識,在Flutter茁壯發展的歷程裏,他們都留下了濃墨重彩的一筆
不得不說,這個控件真的是一個神奇控件,它就彷彿是一把神兵利器app
InheritedWidget這把神兵藏有什麼?框架
InheritedWidget是咱們統稱的一個控件名,精髓仍是InheritedElement,InheritedWidget的數據傳遞,來看下存和取這倆個過程less
存數據
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 = '傳輸數據'; }
取數據
var transferDataElement = context.getElementForInheritedWidgetOfExactType<TransferDataWidget>() as TransferDataElement?; var msg = transferDataElement.value;
能夠發現,咱們只須要經過Element的getElementForInheritedWidgetOfExactType方法,就能夠拿到父節點的TransferDataElement實例(必須繼承InheritedElement)async
拿到實例後,天然就能夠很簡單的拿到相應數據了ide
原理
能夠發現咱們是拿到了XxxInheritedElement實例,繼而拿到了儲存的值,因此關鍵在 getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() 這個方法函數
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; } ... }
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; } }
總體上邏輯仍是比較清晰
遇到某個節點爲 InheritedWidget 時,會將父節點的 _inheritedWidgets 變量給 incomingWidgets 這個臨時變量
爲何任何一個Widget的Element實例的 _inheritedWidgets 變量,可直接拿到父節點InheritedElement實例?
abstract class Element extends DiagnosticableTree implements BuildContext { Map<Type, InheritedElement>? _inheritedWidgets; void _updateInheritance() { assert(_lifecycleState == _ElementLifecycle.active); _inheritedWidgets = _parent?._inheritedWidgets; } ... }
InheritedElement和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
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 方法
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該方法的使用也很簡單
// 舉例 var inheritedElement = context .getElementForInheritedWidgetOfExactType<ChangeNotifierEasyP<T>>() as EasyPInheritedElement<T>?; context.dependOnInheritedElement(inheritedElement);
Provider核心原理,就是採用了InheritedWidget這種刷新機制
想詳細瞭解Provider相關原理,可參考下面文章
圖示
你們在使用InheritedWidget獲取數據的時候,或許有過這樣一種困擾:A頁面 ---> B頁面 ---> C頁面
若是我在A頁面使用InheritedWidget儲存了數據,跳轉到B頁面或者C頁面,會發現使用context獲取不到A頁面的InheritedElement
這側面證實了Navigator路由跳轉:A頁面跳轉B頁面,B頁面並非A頁面的子節點
這裏我畫了下大體結構,若有誤差,請務必指出來,我會盡快修改
關於Flutter路由原理解析,可參考此文章(做者爲啥如今不更文了呢 ~~):Flutter 路由原理解析
InheritedWidget爲咱們帶了不少便利
可是,Element提供的獲取InheritedElement的方式,終究和路由機制沒法很好的結合;這也模塊設計沒法避免的事情,或許某些模塊設計的最優解,很難顧忌到其它模快的一些機制
InheritedWidget這把神兵利器,在咱們學習Flutter歷程中給予了不少幫助
大部分的狀態管理框架,將界面層和邏輯層分開,都是邏輯層來處理界面的刷新;邏輯層能夠交給InheritedWidget存儲管理;說明,咱們本身也必定能夠存儲管理!
這也是GetX中一個核心思想,這並非一個多麼新穎或高深技術,可是,我這以爲這是一種思惟上的突破,能夠帶來更多的可能
依賴注入有以下實現方式(維基百科):
強耦合類型的,基於構造函數
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這個中間層來管理
來看下GetX注入的操做
var controller = Get.put(XxxGetxController());
看看內部操做
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
_singl 這個map存值的時候,不是用的put,而是用的putIfAbsent
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())"'; } } }
S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
看下具體邏輯
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())"'; } } }
爲了知識的連續性,此處簡單的寫下使用
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), ), ); } }
有一天,我躺在牀上思考
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回收實例
經過GetBuilder上泛型獲取相應GetXController實例
autoRemove能夠控制是否自動回收GetXController實例
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!); } }
關鍵步驟
添加監聽代碼
監聽代碼:核心代碼就是getUpdate方法,方法在 GetStateUpdaterMixin 中
圖示
觸發邏輯仍是很簡單的,使用update便可
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類中
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的判斷:有則執行,無則跳過
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); } }
這套刷新機制,和咱們經常使用的狀態管理框架(provider,bloc)以及上面的GetBuilder,在使用上有一些區別
變量上:基礎類型,實體以及列表之類的數據類型,做者都封裝了一套Rx類型,快捷在數據後加obs
使用上:使用這類變量,通常要加上 .value ,做者也給出一個快捷方式變量後面加個 ()
Obx刷新機制,最有趣應該就是變量改變後,包裹該變量的Obx會自動刷新!注意喔,僅僅是包裹該變量的Obx會刷新!其它的Obx並不會刷新。
這是怎麼作到的呢?
簡單的來看下使用
class GetCounterRxLogic extends GetxController { var count = 0.obs; ///自增 void increase() => ++count; }
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), ), ); } }
此處以 RxInt 爲例,來看下其內部實現
extension IntExtension on int { /// Returns a `RxInt` with [this] `int` as initial value. RxInt get obs => RxInt(this); }
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>
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>
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>,上面內容太多了,我這地方簡化下,把須要關注的內容展現出來:此處有幾個須要重點關注的點
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會有刷新操做:刪了不少代碼,保留了重點代碼
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類具備的功能
Obx最大的特殊之處,應該就是使用它的時候,不須要加泛型且能自動刷新,這是怎麼作到的呢?
Obx:代碼並很少,可是皆有妙用
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; }
一個控件想刷新,確定有添加監聽的邏輯,再在某個地方手動觸發
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 類相關,來看下這個類
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; } }
在_ObxState類中作了一個很重要,監聽對象轉移的操做
_observer中的對象已經拿到了Obx控件內部的setState方法,如今須要將它轉移出去啦!
下面貼下將 _observer 中對象轉移出去的代碼:主要的邏輯就是在 notifyChilds 方法中
`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中的幾行代碼都有深意,一行行的解讀下
RxInterface.proxy = _observer:將咱們在 _ObxState類中實例化的 RxNotifier() 對象的地址,賦值給了RxInterface.proxy
final result = widget.build():這個賦值至關重要了!調用咱們在外部傳進的Widget
還記得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); } } }
圖示
Obx的刷新機制,仍是蠻有有趣的
可是,我認爲Obx刷新機制,也是有着自身的缺陷的,從其實現原理上看,這是沒法避免的
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()); } } } }
該模式須要自定義一個基類
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), ), ); } }
自動刷新機制,由於沒加泛型,因此沒法肯定本身內部使用了哪一個注入實例,Getx中是在路由裏面去回收這些實例的,可是,若是你沒使用GetX的路由,又用Obx,你會發現,GetXController竟然沒法自動回收!!!
此處針對該場景,我會給出一種解決方案
在自動刷新的機制中,須要將基礎類型進行封裝
///拓展函數 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] = ''; } } }
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; }
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), ), ), ); } }
這倆種刷新模式,含金量高的,應該仍是自動刷新的機制,思路頗有趣,響應式變量和刷新控件經過靜態變量的形式創建起聯繫,cool!又是一種騷操做!
這倆套狀態管理機制,我都給出了對依賴注入對象,自動回收的解決方案,但願對你們的思路有所啓迪。
終於把最後一篇GetX的原理剖析寫完了(只針對GetX狀態管理這部份內容),了了一樁心事。。。
若是你們認真看完了整片文章,可能會發現:狀態管理+依賴注入,可使得使用場景大大的被拓展
整篇文章寫下來,我真的盡力了
也算是層層遞進的將其中的知識,一點點的展現在你們的面前,但願能夠幫到各位!!!
系列文章 + 相關地址