GetX Obx实现原理探索

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

一.GetX是什么?

GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理框架。

二. 为什么使用GetX?

它不像其他状态管理器那样使用 Streams 或 ChangeNotifier。使用 GetX,一切都是反应式的,没有任何东西依赖于代码生成器,从而提高了开发各个方面的生产力。官方描述如下:

  • 它不依赖于上下文:您可能已经需要将视图的上下文发送到控制器,从而使视图与您的业务逻辑的耦合度很高。您可能不得不对没有上下文的地方使用依赖项,并且必须通过各种类和函数传递上下文。这在 GetX 中是不存在的。您可以在没有任何上下文的情况下从您的控制器中访问您的控制器。您不需要通过参数发送上下文,实际上什么都没有。
  • 精细控制:GetX 专注于性能和最小的资源消耗,大多数状态管理器都基于 ChangeNotifier。当 notifyListeners 被调用时,ChangeNotifier 将通知所有依赖它的小部件。如果你在一个屏幕上有 40 个小部件,其中有一个 ChangeNotifier 类的变量,那么当你更新一个时,它们都会被重建。

三. 用法介绍

GetX 将反应式编程变成了非常简单的东西:

  • 不需要创建 StreamControllers。
  • 无需为每个变量创建 StreamBuilder
  • 不需要为每个状态创建一个类。
  • 无需为初始值创建获取。

使用 Getx 进行反应式编程就像使用 setState 一样简单。

根据官方介绍,假设我们有一个 name 变量,并且希望每次更改它时,所有使用它的小部件都会自动更改。

首先定义我们的变量:

var name = 'Camila';
复制代码

为了使其可观察,只需在其末尾添加“.obs”:

var name = 'Camila'.obs;
复制代码

GetX在幕后做了什么?它创建了一个Streamof String,分配了初始值"Camila",我们通知所有使用"Camila"它们现在“属于”这个变量的小部件,并且当Rx值更改时,它们也必须更改。

这就是GetX 的魔力,这要归功于 Dart 的功能。

但是,正如我们所知,aWidget只有在函数内部才能更改,因为静态类没有“自动更改”的能力。

如果要更改同一范围内的多个变量,您将需要创建一个StreamBuilder,订阅此变量以侦听更改,并创建嵌套的“级联” ,在其他状态管理框架中,您可能需要使用

StreamBuilder( … )? initialValue: …? builder: …? 但在GetX中,你只需要把这个变量放在一个Obx()Widget 中。

Obx (() => Text (controller.name));
复制代码

直接通过箭头函数将该 Widget 传递给( RxObx()的“观察者” )。

Obx非常聪明,只有当值改变时才会改变controller.name

如果name"Spark",并且您将其更改为"Spark"( name.value = "Spark"),与value以前一样,屏幕上不会发生任何变化,并且Obx为了节省资源,将简单地忽略新值而不重建 Widget。

那么,如果我有 5 个Rx(可观察)变量Obx怎么办?

当它们中的任何一个发生变化时,它只会更新发生改变的。

如果我在一个类中有 30 个变量,当我更新一个时,它会更新该类中的所有变量吗?

不,只是使用该Rx变量的特定 Widget 。

四. 原理分析

在用法介绍中,我们主要接触到了.obs与obx。首先我们看一下obs的实现,进入源码分析,可以看到.obs实际上是一个扩展,将变量用RxType的类包装起来,变成了响应式变量。


extension StringExtension on String {

  /// Returns a `RxString` with [this] `String` as initial value.

 RxString get obs => RxString(this);

}


extension IntExtension on int {

  /// Returns a `RxInt` with [this] `int` as initial value.

 RxInt get obs => RxInt(this);

}


extension DoubleExtension on double {

  /// Returns a `RxDouble` with [this] `double` as initial value.

 RxDouble get obs => RxDouble(this);

}


extension BoolExtension on bool {

  /// Returns a `RxBool` with [this] `bool` as initial value.

 RxBool get obs => RxBool(this);

}


extension RxT<T> on T {

  /// Returns a `Rx` instance with [this] `T` as initial value.

 Rx<T> get obs => Rx<T>(this);

}
复制代码

再看Obx包装类,其中,obx继承自ObxWidget,ObxWidget在initstate方法中绑定了观察者订阅关系。在build方法中调用RxInterface.notifyChildren 把_observer作为RxInterface.proxy 的临时属性,在使用builder的后恢复原有的属性, 需要注意的是builder(controller)函数里一定要包含obs.value,否则在if (!observer.canUpdate) 检测时,由于没有观察对象,会抛出提示异常。


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

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {

    super.debugFillProperties(properties);

    properties..add(ObjectFlagProperty<Function>.has('builder', build));

  }


  @override

  _ObxState createState() => _ObxState();


  @protected

  Widget build();

}


class _ObxState extends State<ObxWidget> {

  final _observer = RxNotifier();

  late StreamSubscription subs;

  @override

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

  }


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


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


  @override
  Widget build(BuildContext context) =>

      RxInterface.notifyChildren(_observer, widget.build);
}


class ObxValue<T extends RxInterface> extends ObxWidget {

  final Widget Function(T) builder;

  final T data;


  const ObxValue(this.builder, this.data, {Key? key}) : super(key: key);


  @override

  Widget build() => builder(data);

}


//rx_interface.dart

static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {

    final _observer = RxInterface.proxy;

    RxInterface.proxy = observer;

    final result = builder();

    if (!observer.canUpdate) {

      RxInterface.proxy = _observer;

      throw

      ...

    }

    RxInterface.proxy = _observer;

    return result;

  }
复制代码

了解了它们的具体实现后,我们接下来分析,GetX是如何自动监听变化并进行刷新。

在Obx源码中,我们看到了GetX通过RxNotifier是在initstate方法中绑定了观察者订阅关系,如果监听到变化,会执行 _updateTree 方法然后调用setState去刷新。所以刷新逻辑已经很清晰了(调用setState更新自身),接下来我们需要关注的就是RxNotifier是如何触发监听的。先来看一下RxObjectMixin源码:

//RxObjectMixin相关实现

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;

  bool sentToStream = false;


  /// Same as `toString()` but using a getter.

 String get string => value.toString();


  @override

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


  /// Returns the json representation of `value`.

 dynamic toJson() => value;


  /// This equality override works for _RxImpl instances and the internal

 /// values.

 @override

  // ignore: avoid_equals_and_hash_code_on_mutable_classes

  bool operator ==(Object o) {

    // Todo, find a common implementation for the hashCode of different Types.

    if (o is T) return value == o;

    if (o is RxObjectMixin<T>) return value == o.value;

    return false;

  }


  @override

  // ignore: avoid_equals_and_hash_code_on_mutable_classes

  int get hashCode => _value.hashCode;


  /// Updates the [value] and adds it to the stream, updating the observer

 /// Widget, only if it's different from the previous value.

 set value(T val) {

    if (subject.isClosed) return;

    sentToStream = false;

    if (_value == val && !firstRebuild) return;

    firstRebuild = false;

    _value = val;

    sentToStream = true;

    subject.add(_value);

  }


  /// Returns the current [value]

 T get value {

    RxInterface.proxy?.addListener(subject);

    return _value;

  }


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


 StreamSubscription<T> listenAndPump(void Function(T event) onData,

      {Function? onError, void Function()? onDone, bool? cancelOnError}) {

    final subscription = listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );


    subject.add(value);

    return subscription;

  }


 void bindStream(Stream<T> stream) {

    final listSubscriptions =

        _subscriptions[subject] ??= <StreamSubscription>[];

    listSubscriptions.add(stream.listen((va) => value = va));

  }

}



 /// Base Rx class that manages all the stream logic for any Type.

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 it's not the first rebuild, the listeners have been called already

    // So we won't call them again.

    if (!firstRebuild && !sentToStream) {

      subject.add(v);

    }

  }

}
复制代码

通过浏览上方源码,我们可以分析出,当我们将观察者对象进行.obs后(Rx化后),观察者对象就继承了_RxImpl,而_RxImpl混入了RxObjectMixin,所以每当我们取值时会调用RxObjectMixin 的getter方法,在get方法中RxInterface.proxy添加了对该变量的订阅,当使用.value = xxx更新变量值的时候,就会调用setter方法,如果该被订阅对象已经失效或者被订阅对象的数值没有发生变化又或者已经被重新构建过,则跳过更新,否则将数值附上新的值,set方法最后调用到了GetSteam类中的_notifyData方法,通过遍历onData(onData可以理解为是我们在obx中进行订阅的集合。每当我们在obx中进行监听,就会自动将刚刚的订阅加到onData集合中),遍历完成后调用call方法通知RxNotifier。

再来看一下RxNotifier的实现:

//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;

  /// This is an internal method.

 /// Subscribe to changes on the inner stream.

 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,
      );


  /// Closes the subscriptions for this Rx, releasing the resources.

 void close() {

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

    });


    _subscriptions.clear();

    subject.close();

  }

}



abstract class RxInterface<T> {

  static RxInterface? proxy;


  bool get canUpdate;


  /// Adds a listener to stream

 void addListener(GetStream<T> rxGetx);


  /// Close the Rx Variable

 void close();


  /// Calls `callback` with current value, when the value changes.

 StreamSubscription<T> listen(void Function(T event) onData,

      {Function? onError, void Function()? onDone, bool? cancelOnError});


  /// Avoids an unsafe usage of the `proxy`

 static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {

    final _observer = RxInterface.proxy;

    RxInterface.proxy = observer;

    final result = builder();

    if (!observer.canUpdate) {

      RxInterface.proxy = _observer;

      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;

  }

}
复制代码

当RxNotifier接收到通知后,就会通过listen回调去刷新UI。

五. 总结

使用GetX,可以很灵活的控制页面的刷新粒度,并将复杂的刷新场景简单化。目前GetX在dart pub.dev上受欢迎程度(like数)已经远超provider&bloc等框架。

猜你喜欢

转载自juejin.im/post/7109388162715090958