Flutter Getx 状态管理

状态管理

目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个糟糕的方法。你可以在Flutter官方文档中查到,ChangeNotifier应该使用1个或最多2个监听器,这使得它实际上无法用于任何中等或大型应用。

其他的状态管理器也不错,但有其细微的差别。

  • BLoC非常安全和高效,但是对于初学者来说非常复杂,这使得人们无法使用Flutter进行开发。
  • MobX比BLoC更容易,而且是响应式的,几乎是完美的,但是你需要使用一个代码生成器,对于大型应用来说,这降低了生产力,因为你需要喝很多咖啡,直到你的代码在flutter clean之后再次准备好(这不是MobX的错,而是codegen真的很慢!)。
  • Provider使用InheritedWidget来传递相同的监听器,以此来解决上面报告的ChangeNotifier的问题,这意味着对其ChangeNotifier类的任何访问都必须在widget树内。

Get并不是比任何其他状态管理器更好或更差,而是说你应该分析这些要点以及下面的要点来选择是只用Get,还是与其他状态管理器结合使用。Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,它的状态管理功能既可以单独使用,也可以与其他状态管理器结合使用。

响应式状态管理器

响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。

  • 你不需要创建StreamControllers.
  • 你不需要为每个变量创建一个StreamBuilder。
  • 你不需要为每个状态创建一个类。
  • 你不需要为一个初始值创建一个get。

使用 Get 的响应式编程就像使用 setState 一样简单。

让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。

这是你的计数变量:

var name = 'Jonatas Borges';

要想让它变得可观察,你只需要在它的末尾加上”.obs”。

var name = 'Jonatas Borges'.obs;

就这么简单!

我们把这个reactive-“.obs”(ervables)变量称为_Rx_。

我们做了什么?我们创建了一个 “Stream “的 “String”,分配了初始值 “Jonatas Borges”,我们通知所有使用 “Jonatas Borges “的widgets,它们现在 “属于 “这个变量,当_Rx_的值发生变化时,它们也要随之改变。

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

但是,我们知道,一个Widget只有在函数里面才能改变,因为静态类没有 “自动改变 “的能力。

你需要创建一个StreamBuilder,订阅这个变量来监听变化,如果你想在同一个范围内改变几个变量,就需要创建一个 “级联 “的嵌套StreamBuilder,对吧?

不,你不需要一个StreamBuilder,但你对静态类的理解是对的。

在视图中,当我们想改变一个特定的Widget时,我们通常有很多Flutter方式的模板。 有了GetX,你也可以忘记这些模板代码了。

StreamBuilder( ... )initialValue: ...builder: ...? 不,你只需要把这个变量放在Obx()这个Widget里面就可以了。

Obx (() => Text (controller.name));

_你只需记住 Obx(()=>

你只需将Widget通过一个箭头函数传递给 Obx()(_Rx_的 “观察者”)。

Obx是相当聪明的,只有当controller.name的值发生变化时才会改变。

如果name"John",你把它改成了"John"name.value="John"),因为它和之前的value是一样的,所以界面上不会有任何变化,而Obx为了节省资源,会直接忽略新的值,不重建Widget。这是不是很神奇

那么,如果我在一个Obx里有5个_Rx_(可观察的)变量呢?

当其中任何一个变量发生变化时,它就会更新。

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

不会,只会更新使用那个 Rx 变量的特定 Widget

所以,只有当_Rx_变量的值发生变化时,GetX才会更新界面。

final isOpen = false.obs;

//什么都不会发生......相同的值。
void onButtonTap() => isOpen.value=false;

优势

当你需要对更新的内容进行精细的控制时,GetX() 可以帮助你。

如果你不需要 “unique IDs”,比如当你执行一个操作时,你的所有变量都会被修改,那么就使用GetBuilder。 因为它是一个简单的状态更新器(以块为单位,比如setState()),只用几行代码就能完成。 它做得很简单,对CPU的影响最小,只是为了完成一个单一的目的(一个_State_ Rebuild),并尽可能地花费最少的资源。

如果你需要一个强大的状态管理器,用GetX是不会错的。

它不能和变量一起工作,除了__flows__,它里面的东西本质都是Streams。 你可以将_rxDart_与它结合使用,因为所有的东西都是Streams。 你可以监听每个”_Rx_变量 “的 “事件”。 因为里面的所有东西都是 “Streams”。

这实际上是一种_BLoC_方法,比_MobX_更容易,而且没有代码生成器或装饰。 你可以把任何东西变成一个_”Observable”_,只需要在它末尾加上.obs

最高性能

除了有一个智能的算法来实现最小化的重建,GetX还使用了比较器以确保状态已经改变。

如果你的应用程序中遇到错误,并发送重复的状态变更,GetX将确保它不会崩溃。

使用GetX,只有当value改变时,状态才会改变。 这就是GetX,和使用MobX_的_computed的主要区别。 当加入两个__observable__,其中一个发生变化时,该_observable_的监听器也会发生变化。

使用GetX,如果你连接了两个变量,GetX()(类似于Observer())只有在它的状态真正变化时才会重建。

声明一个响应式变量

你有3种方法可以把一个变量变成是 “可观察的”。

1 – 第一种是使用 Rx{Type}

// 建议使用初始值,但不是强制性的
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});

2 – 第二种是使用 Rx,规定泛型 Rx<Type>

final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});

// 自定义类 - 可以是任何类
final user = Rx<User>();

3 – 第三种更实用、更简单、更可取的方法,只需添加 .obs 作为value的属性。

final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;

// 自定义类 - 可以是任何类
final user = User().obs;
有一个反应的状态,很容易。

我们知道,_Dart_现在正朝着_null safety_的方向发展。 为了做好准备,从现在开始,你应该总是用一个初始值来开始你的_Rx_变量。

GetX将一个变量转化为一个_observable_ + _initial value_是最简单,也是最实用的方法。

你只需在变量的末尾添加一个”.obs“,即可把它变成可观察的变量, 然后它的.value就是_初始值_)。

使用视图中的值

// controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
// 视图
GetX<Controller>(
  builder: (controller) {
    print("count 1 rebuild");
    return Text('${controller.count1.value}');
  },
),
GetX<Controller>(
  builder: (controller) {
    print("count 2 rebuild");
    return Text('${controller.count2.value}');
  },
),
GetX<Controller>(
  builder: (controller) {
    print("count 3 rebuild");
    return Text('${controller.sum}');
  },
),

如果我们把count1.value++递增,就会打印出来:

  • count 1 rebuild
  • count 3 rebuild

如果我们改变count2.value++,就会打印出来。

  • count 2 rebuild
  • count 3 rebuild

因为count2.value改变了,sum的结果现在是2

  • 注意:默认情况下,第一个事件将重建小组件,即使是相同的。 这种行为是由于布尔变量而存在的。

想象一下你这样做。

var isLogged = false.obs;

然后,你检查用户是否 “登录”,以触发ever的事件。

@override
onInit(){
  ever(isLogged, fireRoute);
  isLogged.value = await Preferences.hasToken();
}

fireRoute(logged) {
  if (logged) {
   Get.off(Home());
  } else {
   Get.off(Login());
  }
}

如果 “hasToken “是 “false”,”isLogged “就不会有任何变化,所以 “ever() “永远不会被调用。 为了避免这种问题,_observable_的第一次变化将总是触发一个事件,即使它包含相同的.value

如果你想删除这种行为,你可以使用: isLogged.firstRebuild = false;

什么时候重建

此外,Get还提供了精细的状态控制。你可以根据特定的条件对一个事件进行条件控制(比如将一个对象添加到List中)。

// 第一个参数:条件,必须返回true或false。
// 第二个参数:如果条件为真,则为新的值。
list.addIf(item < limit, item);

没有装饰,没有代码生成器,没有复杂的程序: 爽歪歪!

你知道Flutter的计数器应用吗?你的Controller类可能是这样的。

class CountController extends GetxController {
  final count = 0.obs;
}

用一个简单的。

controller.count.value++

你可以在你的UI中更新计数器变量,不管它存储在哪里。

可以使用.obs的地方

你可以在 obs 上转换任何东西,这里有两种方法:

  • 可以将你的类值转换为 obs
class RxUser {
  final name = "Camila".obs;
  final age = 18.obs;
}
  • 或者可以将整个类转换为一个可观察的类。
class User {
  User({String name, int age});
  var name;
  var age;
}

//实例化时。
final user = User(name: "Camila", age: 18).obs;

关于List的说明

List和它里面的对象一样,是完全可以观察的。这样一来,如果你在List中添加一个值,它会自动重建使用它的widget。

你也不需要在List中使用”.value”,神奇的dart api允许我们删除它。 不幸的是,像String和int这样的原始类型不能被扩展,使得.value的使用是强制性的,但是如果你使用get和setter来处理这些类型,这将不是一个问题。

// controller
final String title = 'User Info:'.obs
final list = List<User>().obs;

// view
Text(controller.title.value), // 字符串后面需要有.value。
ListView.builder (
  itemCount: controller.list.length // List不需要它
)

当你在使自己的类可观察时,有另外一种方式来更新它们:

// model
// 我们将使整个类成为可观察的,而不是每个属性。
class User{
    User({this.name = '', this.age = 0});
    String name;
    int age;
}

// controller
final user = User().obs;
//当你需要更新user变量时。
user.update( (user) { // 这个参数是你要更新的类本身。
    user.name = 'Jonny';
    user.age = 18;
});
// 更新user变量的另一种方式。
user(User(name: 'João', age: 35));

// view
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"));
// 你也可以不使用.value来访问模型值。
user().name; // 注意是user变量,而不是类变量(首字母是小写的)。

你可以使用 “assign “和” assignAll “。 “assign “会清除你的List,并添加一个单个对象。 “assignAll “将清除现有的List,并添加任何可迭代对象。

为什么使用.value

我们可以通过一个简单的装饰和代码生成器来消除使用”String “和 “int “的值的义务,但这个库的目的正是为了避免外部依赖。我们希望提供一个准备好的编程的环境(路由、依赖和状态的管理),以一种简单、轻量级和高性能的方式,而不需要一个外部包。

你可以在你的pubspec中添加3个字母(get)和一个冒号,然后开始编程。从路由管理到状态管理,所有的解决方案都是默认的,目的是为了方便、高效和高性能。

这个库的总大小还不如一个状态管理器,尽管它是一个完整的解决方案。

如果你被.value困扰,喜欢代码生成器,MobX是一个很好的选择,也可以和Get一起使用。对于那些想在pubspec中添加一个单一的依赖,然后开始编程,而不用担心一个包的版本与另一个包不兼容,也不用担心状态更新的错误来自于状态管理器或依赖,还是不想担心控制器的可用性,get都是刚刚好。

如果你对MobX代码生成器或者BLoC模板熟悉,你可以直接用Get来做路由,而忘记它有状态管理器。如果有一个项目有90多个控制器,MobX代码生成器在标准机器上进行Flutter Clean后,需要30多分钟才能完成任务。如果你的项目有5个、10个、15个控制器,任何一个状态管理器都能很好的满足你。如果你的项目大得离谱,代码生成器对你来说是个问题,但你已经获得了Get这个解决方案。

显然,如果有人想为项目做贡献,创建一个代码生成器,或者类似的东西,我将在这个readme中链接,作为一个替代方案,我的需求并不是所有开发者的需求,但现在我要说的是,已经有很好的解决方案,比如MobX。

Obx()

在Get中使用Bindings的类型是不必要的。你可以使用Obx widget代替GetX,GetX只接收创建widget的匿名函数。 如果你不使用类型,你将需要有一个控制器的实例来使用变量,或者使用Get.find<Controller>().value或Controller.to.value来检索值。

Workers

Workers将协助你在事件发生时触发特定的回调。

///每次`count1`变化时调用。
ever(count1, (_) => print("$_ has been changed"));

///只有在变量$_第一次被改变时才会被调用。
once(count1, (_) => print("$_ was changed once"));

///防DDos - 每当用户停止输入1秒时调用,例如。
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));

///忽略1秒内的所有变化。
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));

所有Workers(除 “debounce “外)都有一个名为 “condition”的参数,它可以是一个 “bool “或一个返回 “bool “的回调。 这个condition定义了callback函数何时执行。

所有worker都会返回一个Worker实例,你可以用它来取消(通过dispose())worker。

  • ever 每当_Rx_变量发出一个新的值时,就会被调用。
  • everAll 和 “ever “很像,但它需要一个_Rx_值的 “List”,每次它的变量被改变时都会被调用。就是这样。
  • once ‘once’只在变量第一次被改变时被调用。
  • debounce debounce’在搜索函数中非常有用,你只希望API在用户完成输入时被调用。如果用户输入 “Jonny”,你将在API中进行5次搜索,分别是字母J、o、n、n和y。使用Get不会发生这种情况,因为你将有一个 “debounce “Worker,它只会在输入结束时触发。
  • interval ‘interval’与debouce不同,debouce如果用户在1秒内对一个变量进行了1000次修改,他将在规定的计时器(默认为800毫秒)后只发送最后一次修改。Interval则会忽略规定时间内的所有用户操作。如果你发送事件1分钟,每秒1000个,那么当用户停止DDOS事件时,debounce将只发送最后一个事件。建议这样做是为了避免滥用,在用户可以快速点击某样东西并获得一些好处的功能中(想象一下,用户点击某样东西可以赚取硬币,如果他在同一分钟内点击300次,他就会有300个硬币,使用间隔,你可以设置时间范围为3秒,无论是点击300次或100万次,1分钟内他最多获得20个硬币)。debounce适用于防DDOS,适用于搜索等功能,每次改变onChange都会调用你的api进行查询。Debounce会等待用户停止输入名称,进行请求。如果在上面提到的投币场景中使用它,用户只会赢得1个硬币,因为只有当用户 “暂停 “到既定时间时,它才会被执行。
  • 注意:Worker应该总是在启动Controller或Class时使用,所以应该总是在onInit(推荐)、Class构造函数或StatefulWidget的initState(大多数情况下不推荐这种做法,但应该不会有任何副作用)。

简单状态管理器

Get有一个极其轻巧简单的状态管理器,它不使用ChangeNotifier,可以满足特别是对Flutter新手的需求,而且不会给大型应用带来问题。

GetBuilder正是针对多状态控制的。想象一下,你在购物车中添加了30个产品,你点击删除一个,同时List更新了,价格更新了,购物车中的徽章也更新为更小的数字。这种类型的方法使GetBuilder成为杀手锏,因为它将状态分组并一次性改变,而无需为此进行任何 “计算逻辑”。GetBuilder就是考虑到这种情况而创建的,因为对于短暂的状态变化,你可以使用setState,而不需要状态管理器。

这样一来,如果你想要一个单独的控制器,你可以为其分配ID,或者使用GetX。这取决于你,记住你有越多的 “单独 “部件,GetX的性能就越突出,而当有多个状态变化时,GetBuilder的性能应该更优越。

优点

  1. 只更新需要的小部件。
  2. 不使用changeNotifier,状态管理器使用较少的内存(接近0mb)。
  3. 忘掉StatefulWidget! 使用Get你永远不会需要它。对于其他的状态管理器,你可能需要使用StatefulWidget来获取你的Provider、BLoC、MobX控制器等的实例。但是你有没有停下来想一想,你的appBar,你的脚手架,以及你的类中的大部分widget都是无状态的?那么如果你只能保存有状态的Widget的状态,为什么要保存整个类的状态呢?Get也解决了这个问题。创建一个无状态类,让一切都成为无状态。如果你需要更新单个组件,就用GetBuilder把它包起来,它的状态就会被维护。
  4. 真正的解耦你的项目! 控制器一定不要在你的UI中,把你的TextEditController,或者你使用的任何控制器放在你的Controller类中。
  5. 你是否需要触发一个事件来更新一个widget,一旦它被渲染?GetBuilder有一个属性 “initState”,就像StatefulWidget一样,你可以从你的控制器中调用事件,直接从控制器中调用,不需要再在你的initState中放置事件。
  6. 你是否需要触发一个动作,比如关闭流、定时器等?GetBuilder也有dispose属性,只要该widget被销毁,你就可以调用事件。
  7. 仅在必要时使用流。你可以在你的控制器里面正常使用你的StreamControllers,也可以正常使用StreamBuilder,但是请记住,一个流消耗合理的内存,响应式编程很美,但是你不应该滥用它。30个流同时打开会比changeNotifier更糟糕(而且changeNotifier非常糟糕)。
  8. 更新widgets而不需要为此花费ram。Get只存储GetBuilder的创建者ID,必要时更新该GetBuilder。get ID存储在内存中的消耗非常低,即使是成千上万的GetBuilders。当你创建一个新的GetBuilder时,你实际上是在共享拥有创建者ID的GetBuilder的状态。不会为每个GetBuilder创建一个新的状态,这为大型应用节省了大量的内存。基本上你的应用程序将是完全无状态的,而少数有状态的Widgets(在GetBuilder内)将有一个单一的状态,因此更新一个状态将更新所有的状态。状态只是一个。
  9. Get是全知全能的,在大多数情况下,它很清楚地知道从内存中取出一个控制器的时机,你不需要担心什么时候移除一个控制器,Get知道最佳的时机。

用法

// 创建控制器类并扩展GetxController。
class Controller extends GetxController {
  int counter = 0;
  void increment() {
    counter++;
    update(); // 当调用增量时,使用update()来更新用户界面上的计数器变量。
  }
}
// 在你的Stateless/Stateful类中,当调用increment时,使用GetBuilder来更新Text。
GetBuilder<Controller>(
  init: Controller(), // 首次启动
  builder: (_) => Text(
    '${_.counter}',
  ),
)
//只在第一次时初始化你的控制器。第二次使用ReBuilder时,不要再使用同一控制器。一旦将控制器标记为 "init "的部件部署完毕,你的控制器将自动从内存中移除。你不必担心这个问题,Get会自动做到这一点,只是要确保你不要两次启动同一个控制器。

完成!

  • 你已经学会了如何使用Get管理状态。
  • 注意:你可能想要一个更大的规模,而不是使用init属性。为此,你可以创建一个类并扩展Bindings类,并在其中添加将在该路由中创建的控制器。控制器不会在那个时候被创建,相反,这只是一个声明,这样你第一次使用Controller时,Get就会知道去哪里找。Get会保持懒加载,当不再需要Controller时,会自动移除它们。请看pub.dev的例子来了解它是如何工作的。

如果你导航了很多路由,并且需要之前使用的Controller中的数据,你只需要再用一次GetBuilder(没有init)。

class OtherClass extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GetBuilder<Controller>(
          builder: (s) => Text('${s.counter}'),
        ),
      ),
    );
  }

如果你需要在许多其他地方使用你的控制器,并且在GetBuilder之外,只需在你的控制器中创建一个get,就可以轻松地拥有它。(或者使用Get.find<Controller>())

class Controller extends GetxController {

  /// 你不需要这个,我推荐使用它只是为了方便语法。
  /// 用静态方法:Controller.to.increment()。
  /// 没有静态方法的情况下:Get.find<Controller>().increment();
  /// 使用这两种语法在性能上没有区别,也没有任何副作用。一个不需要类型,另一个IDE会自动完成。
  static Controller get to => Get.find(); // 添加这一行

  int counter = 0;
  void increment() {
    counter++;
    update();
  }
}

然后你可以直接访问你的控制器,这样:

FloatingActionButton(
  onPressed: () {
    Controller.to.increment(),
  } // 是不是贼简单!
  child: Text("${Controller.to.counter}"),
),

当你按下FloatingActionButton时,所有监听’counter’变量的widget都会自动更新。

如何处理controller

比方说,我们有这样的情况。

Class a => Class B (has controller X) => Class C (has controller X)

在A类中,控制器还没有进入内存,因为你还没有使用它(Get是懒加载)。在类B中,你使用了控制器,并且它进入了内存。在C类中,你使用了与B类相同的控制器,Get会将控制器B的状态与控制器C共享,同一个控制器还在内存中。如果你关闭C屏和B屏,Get会自动将控制器X从内存中移除,释放资源,因为a类没有使用该控制器。如果你再次导航到B,控制器X将再次进入内存,如果你没有去C类,而是再次回到a类,Get将以同样的方式将控制器从内存中移除。如果类C没有使用控制器,你把类B从内存中移除,就没有类在使用控制器X,同样也会被处理掉。唯一能让Get乱了阵脚的例外情况,是如果你意外地从路由中删除了B,并试图使用C中的控制器,在这种情况下,B中的控制器的创建者ID被删除了,Get被设计为从内存中删除每一个没有创建者ID的控制器。如果你打算这样做,在B类的GetBuilder中添加 “autoRemove: false “标志,并在C类的GetBuilder中使用adopID = true;

无需StatefulWidgets

使用StatefulWidgets意味着不必要地存储整个界面的状态,甚至因为如果你需要最小化地重建一个widget,你会把它嵌入一个Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx中,这将是另一个StatefulWidget。 StatefulWidget类是一个比StatelessWidget大的类,它将分配更多的RAM,只使用一两个类之间可能不会有明显的区别,但当你有100个类时,它肯定会有区别! 除非你需要使用混合器,比如TickerProviderStateMixin,否则完全没有必要使用StatefulWidget与Get。

你可以直接从GetBuilder中调用StatefulWidget的所有方法。 例如,如果你需要调用initState()或dispose()方法,你可以直接调用它们。

GetBuilder<Controller>(
  initState: (_) => Controller.to.fetchApi(),
  dispose: (_) => Controller.to.closeStreams(),
  builder: (s) => Text('${s.username}'),
),

比这更好的方法是直接从控制器中使用onInit()和onClose()方法。

@override
void onInit() {
  fetchApi();
  super.onInit();
}
  • 注意:如果你想在控制器第一次被调用的那一刻启动一个方法,你不需要为此使用构造函数,使用像Get这样面向性能的包,这样做反而是糟糕的做法,因为它偏离了控制器被创建或分配的逻辑(如果你创建了这个控制器的实例,构造函数会立即被调用,你会在控制器还没有被使用之前就填充了一个控制器,你在没有被使用的情况下就分配了内存,这绝对违背这个库的原则)。onInit();和onClose();方法就是为此而创建的,它们会在Controller被创建,或者第一次使用时被调用,这取决于你是否使用Get.lazyPut。例如,如果你想调用你的API来填充数据,你可以忘掉老式的initState/dispose方法,只需在onInit中开始调用api,如果你需要执行任何命令,如关闭流,使用onClose()来实现。

为什么它存在

这个包的目的正是为了给你提供一个完整的解决方案,用于导航路线,管理依赖和状态,使用尽可能少的依赖,高度解耦。Get将所有高低级别的Flutter API都纳入自身,以确保你在工作中尽可能减少耦合。我们将所有的东西集中在一个包中,以确保你在你的项目中没有任何形式的耦合。这样一来,你就可以只在视图中放置小组件,而让你的团队中负责业务逻辑的那部分人自由地工作,不需要依赖视图中的任何元素来处理业务逻辑。这样就提供了一个更加干净的工作环境,这样你的团队中的一部分人只用widget工作,而不用担心将数据发送到你的controller,你的团队中的一部分人只用业务逻辑工作,而不依赖于视图的任何元素。

所以为了简化这个问题。 你不需要在initState中调用方法,然后通过参数发送给你的控制器,也不需要使用你的控制器构造函数,你有onInit()方法,在合适的时间被调用,以启动你的服务。 你不需要调用设备,你有onClose()方法,它将在确切的时刻被调用,当你的控制器不再需要时,将从内存中删除。这样一来,只给widget留下视图,不要从中进行任何形式的业务逻辑。

不要在GetxController里面调用dispose方法,它不会有任何作用,记住控制器不是Widget,你不应该 “dispose “它,它会被Get自动智能地从内存中删除。如果你在上面使用了任何流,想关闭它,只要把它插入到close方法中就可以了。例如

class Controller extends GetxController {
  StreamController<User> user = StreamController<User>();
  StreamController<String> name = StreamController<String>();

  ///关闭流用onClose方法,而不是dispose
  @override
  void onClose() {
    user.close();
    name.close();
    super.onClose();
  }
}

控制器的生命周期。

  • onInit()是创建控制器的地方。
  • onClose(),关闭控制器,为删除方法做准备。
  • deleted: 你不能访问这个API,因为它实际上是将控制器从内存中删除。它真的被删除了,不留任何痕迹。

其他使用方法

你可以直接在GetBuilder值上使用Controller实例。

GetBuilder<Controller>(
  init: Controller(),
  builder: (value) => Text(
    '${value.counter}', //here
  ),
),

你可能还需要在GetBuilder之外的控制器实例,你可以使用这些方法来实现。

class Controller extends GetxController {
  static Controller get to => Get.find();
[...]
}
//view
GetBuilder<Controller>(  
  init: Controller(), // 每个控制器只用一次
  builder: (_) => Text(
    '${Controller.to.counter}', //here
  )
),

或者

class Controller extends GetxController {
 // static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(  
  init: Controller(), // 每个控制器只用一次
  builder: (_) => Text(
    '${Get.find<Controller>().counter}', //here
  ),
),
  • 你可以使用 “非规范 “的方法来做这件事。如果你正在使用一些其他的依赖管理器,比如get_it、modular等,并且只想交付控制器实例,你可以这样做。
Controller controller = Controller();
[...]
GetBuilder<Controller>(
  init: controller, //here
  builder: (_) => Text(
    '${controller.counter}', // here
  ),
),

唯一的ID

如果你想用GetBuilder完善一个widget的更新控件,你可以给它们分配唯一的ID。

GetBuilder<Controller>(
  id: 'text', //这里
  init: Controller(), // 每个控制器只用一次
  builder: (_) => Text(
    '${Get.find<Controller>().counter}', //here
  ),
),

并更新它:

update(['text']);

您还可以为更新设置条件。

update(['text'], counter < 10);

GetX会自动进行重建,并且只重建使用被更改的变量的小组件,如果您将一个变量更改为与之前相同的变量,并且不意味着状态的更改,GetX不会重建小组件以节省内存和CPU周期(界面上正在显示3,而您再次将变量更改为3。在大多数状态管理器中,这将导致一个新的重建,但在GetX中,如果事实上他的状态已经改变,那么widget将只被再次重建)

与其他状态管理器混用

有人开了一个功能请求,因为他们只想使用一种类型的响应式变量,而其他的则手动去更新,需要为此在GetBuilder中插入一个Obx。思来想去,MixinBuilder应运而生。它既可以通过改变”.obs “变量进行响应式改变,也可以通过update()进行手动更新。然而,在4个widget中,他是消耗资源最多的一个,因为除了有一个Subscription来接收来自他的子代的变化事件外,他还订阅了他的控制器的update方法。

扩展GetxController是很重要的,因为它们有生命周期,可以在它们的onInit()和onClose()方法中 “开始 “和 “结束 “事件。你可以使用任何类来实现这一点,但我强烈建议你使用GetxController类来放置你的变量,无论它们是否是可观察的。

GetBuilder vs GetX vs Obx vs MixinBuilder

在十年的编程工作中,我能够学到一些宝贵的经验。

我第一次接触到响应式编程的时候,是那么的 “哇,这太不可思议了”,事实上响应式编程是不可思议的。 但是,它并不适合所有情况。通常情况下,你需要的是同时改变2、3个widget的状态,或者是短暂的状态变化,这种情况下,响应式编程不是不好,而是不适合。

响应式编程对RAM的消耗比较大,可以通过单独的工作流来弥补,这样可以保证只有一个widget被重建,而且是在必要的时候,但是创建一个有80个对象的List,每个对象都有几个流,这不是一个好的想法。打开dart inspect,查看一个StreamBuilder的消耗量,你就会明白我想告诉你什么。

考虑到这一点,我创建了简单的状态管理器。它很简单,这正是你应该对它提出的要求:以一种简单的方式,并且以最高效的方式更新块中的状态。

GetBuilder在RAM中是非常高效的,几乎没有比他更高效的方法(至少我无法想象,如果存在,请告诉我们)。

然而,GetBuilder仍然是一个手动的状态管理器,你需要调用update(),就像你需要调用Provider的notifyListeners()一样。

还有一些情况下,响应式编程真的很有趣,不使用它就等于重新发明轮子。考虑到这一点,GetX的创建是为了提供状态管理器中最现代和先进的一切。它只在必要的时候更新必要的东西,如果你出现了错误,同时发送了300个状态变化,GetX只在状态真正发生变化时才会过滤并更新界面。

GetX比其他响应式状态管理器还是比较高效的,但它比GetBuilder多消耗一点内存。思前想后,以最大限度地消耗资源为目标,Obx应运而生。与GetX和GetBuilder不同的是,你将无法在Obx内部初始化一个控制器,它只是一个带有StreamSubscription的Widget,接收来自你的子代的变化事件,仅此而已。它比GetX更高效,但输给了GetBuilder,这是意料之中的,因为它是响应式的,而且GetBuilder有最简单的方法,即存储widget的hashcode和它的StateSetter。使用Obx,你不需要写你的控制器类型,你可以从多个不同的控制器中监听到变化,但它需要在之前进行初始化,或者使用本readme开头的示例方法,或者使用Bindings类。

THE END
喜欢就支持一下吧
点赞5 分享