flutter状态管理插件GetX中GetBuilder和 GetX、Obx不同的差别

Flutter的状态管理插件GetX是一个非常强大的插件,不仅有状态管理的功能,还带有路由、依赖管理、多语言等一系列有用的功能。但是GetX中的状态管理一共有三种方式:GetBuilder,GetX,Obx,那么它们之间又有什么不同呢?

官方文档的解释:

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

我第一次接触反应式编程是“哇,这太不可思议了”,事实上反应式编程是不可思议的。但是,它并不适合所有情况。通常,您只需要同时更改 2 或 3 个小部件的状态,或者状态的短暂更改,在这种情况下,响应式编程还不错,但不合适。

响应式编程具有更高的 RAM内存 消耗,可以通过单个工作流进行补偿,这将确保仅重建一个小部件并在必要时重建,但是创建一个包含 80 个对象的列表,每个对象都有多个流并不是一个好主意。打开 dart 检查器并检查 StreamBuilder 消耗了多少,您就会明白我想告诉您的内容。

考虑到这一点,我创建了简单的状态管理器。它很简单,这正是您应该要求的:以简单的方式和最经济的方式更新块中的状态。

GetBuilder在RAM上非常省钱,几乎没有比他更省钱的方法了(至少我想不出一个,如果存在请告知)。

但是,GetBuilder 仍然是一个机械状态管理器,您需要像调用 Provider 的 notifyListeners() 一样调用 update(),亦即需要读取数据之后手动更新

在其他情况下,响应式编程真的很有趣,不使用它就等于重新发明轮子。考虑到这一点,GetX 旨在提供状态管理器中最现代和最先进的一切。它仅更新必要的内容,并且在必要时,如果您有错误并同时发送 300 个状态更改,GetX 将仅在状态实际更改时过滤和更新屏幕。

GetX 仍然比任何其他反应式状态管理器更经济,但它比 GetBuilder 消耗更多的 RAM内存。考虑一下,旨在最大限度地消耗Obx创建的资源。与 GetX 和 GetBuilder 不同,您将无法在 Obx 中初始化控制器,它只是一个带有 StreamSubscription 的 Widget,用于从您的孩子组建接收更改事件,仅此而已。它比 GetX 更经济,但输给了 GetBuilder,这是意料之中的,因为它是反应式的,并且 GetBuilder 具有现有的最简单的方法,即存储小部件的哈希码及其 StateSetter。使用Obx你不需要写你的控制器类型,你可以听到来自多个不同控制器的变化,但它需要之前被初始化,

总结一下:

内存使用量:GetX>Obx>GetBuilder

关键点:GetBuilder需要在控制器中手动使用update()告知有数据更改。

使用方式:

GetBuilder:

// Create controller class and extends GetxController
class Controller extends GetxController {
  int counter = 0;
  void increment() {
    counter++;
    update(); // use update() to update counter variable on UI when increment be called
  }
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
  init: Controller(), // INIT IT ONLY THE FIRST TIME
  builder: (_) => Text(
    '${_.counter}',
  ),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.

Obx:

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

// on the view
Text(controller.title.value), // String need to have .value in front of it
ListView.builder (
  itemCount: controller.list.length // lists don't need it
)
// on the model file
// we are going to make the entire class observable instead of each attribute
class User() {
  User({this.name = '', this.age = 0});
  String name;
  int age;
}

// on the controller file
final user = User().obs;
// when you need to update the user variable:
user.update( (user) { // this parameter is the class itself that you want to update
user.name = 'Jonny';
user.age = 18;
});
// an alternative way of update the user variable:
user(User(name: 'João', age: 35));

// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// you can also access the model values without the .value:
user().name; // notice that is the user variable, not the class (variable has lowercase u)
class Controller extends GetxController{
  var count = 0.obs;
  increment() => count++;
}

class Home extends StatelessWidget {

  @override
  Widget build(context) {

    // Instantiate your class using Get.put() to make it available for all "child" routes there.
    final Controller c = Get.put(Controller());

    return Scaffold(
      // Use Obx(()=> to update Text() whenever count is changed.
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

      // Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context
      body: Center(child: ElevatedButton(
              child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
      floatingActionButton:
          FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
  }
}

class Other extends StatelessWidget {
  // You can ask Get to find a Controller that is being used by another page and redirect you to it.
  final Controller c = Get.find();

  @override
  Widget build(context){
     // Access the updated count variable
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}

GetX:

// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
// view file
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}');
  },
),

额外知识

如何创建响应式变量

1 - 第一个是使用Rx{Type}.

// initial value is recommended, but not mandatory
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和使用 Darts 泛型,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>>({});

// Custom classes - it can be any class, literally
final user = Rx<User>();

3 - 第三种,更实用,更简单和首选的方法,只需为您的属性value添加.obs

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;

// Custom classes - it can be any class, literally
final user = User().obs;

Worker用法:

Worker可以精确控制事件发生时触发回调,常用于Controller的onInit中:

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

fireRoute(logged) {
  if (logged) {
   Get.off(Home());
  } else {
   Get.off(Login());
  }
}
///每次`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(大多数情况下不推荐这种做法,但应该不会有任何副作用)。