Flutter GetX - 状态管理

发表于:2021-07-21
字数统计:15.6k 字
阅读时长:39 分钟
阅读量:357

前言

Flutter的很多灵感来自于React,它的设计思想是数据与视图分离,由数据映射渲染视图。所以在Flutter中,它的Widget是immutable的,而它的动态部分全部放到了状态(State)中。于是状态管理自然便成了我们密切关注的对象。

为什么需要状态管理?

在我们一开始构建应用的时候,也许很简单。我们有一些状态,直接把他们映射成视图就可以了。这种简单应用可能并不需要状态管理。

但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样

Wow,这是什么鬼。我们很难再清楚的测试维护我们的状态,因为它看上去实在是太复杂了!而且还会有多个页面共享同一个状态,

例如当你进入一个文章点赞,退出到外部缩略展示的时候,外部也需要显示点赞数,这时候就需要同步这两个状态。

这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。

Getx状态管理

目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier来更新widget,这对于中大型应用的性能来说是一个糟糕的方法。

参考:

状态管理:https://github.com/jonataslaw/getx/blob/master/documentation/zh_CN/state_management.md

响应式状态管理器

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

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

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

这是你的计数变量:

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种方法可以把一个变量变成是 "可观察的"。

第一种是使用 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>({});

第二种是使用 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>();

第三种更实用、更简单、更可取的方法,只需添加 .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;

Obx

lib/pages/state_obx/index.dart

class StateObxView extends StatelessWidget {
  StateObxView({Key? key}) : super(key: key);

  final count = 0.obs;

  @overrideWidget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Obx(...)"),
      ),
      body: Center(
        child: Column(
          children: [
            Obx(() => Text("count1 -> " + count.toString())),
            Obx(() => Text("count2 -> " + count.toString())),

            //Divider(),ElevatedButton(
              onPressed: () {
                count.value++;
              },
              child: Text('add'),
            ),
          ],
        ),
      ),
    );
  }
}

obs、extension、RxInt、Rx

...

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` instace with [this] `T` as initial value.
  Rx<T> get obs => Rx<T>(this);
}

小结:

适合界面上 简单状态管理,写起来很快

GetX

编写控制器 lib/pages/state_getx/controller.dart

class CountController extends GetxController {
  final _count = 0.obs;
  set count(value) => this._count.value = value;
  get count => this._count.value;

  final _count2 = 0.obs;
  set count2(value) => this._count2.value = value;
  get count2 => this._count2.value;

  add() => _count.value++;
  add2() => _count2.value++;
}

编写视图 lib/pages/state_getx/index.dart

class StateGetxView extends StatelessWidget {
  StateGetxView({Key? key}) : super(key: key);

  final controller = CountController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Getx"),
      ),
      body: Center(
        child: Column(
          children: [
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetX - 1");
                return Text('value 1 -> ${_.count}');
              },
            ),
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetX - 2");
                return Text('value 2 -> ${_.count}');
              },
            ),
            Divider(),

            //
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetX - 3");
                return Column(
                  children: [
                    Text('value 3 -> ${_.count}'),
                    ElevatedButton(
                      onPressed: () {
                        _.add();
                      },
                      child: Text('count1'),
                    )
                  ],
                );
              },
            ),
            Divider(),

            // count2
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetX - 4");
                return Text('value 4 -> ${_.count2}');
              },
            ),
            Divider(),

            // 按钮
            ElevatedButton(
              onPressed: () {
                controller.add();
              },
              child: Text('count1'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.add2();
              },
              child: Text('count2'),
            ),
          ],
        ),
      ),
    );
  }
}

小结:

适合控制多控制器、多状态更新,可精细控制初始、局部渲染。

GetBuilder

控制器 lib/pages/state_getBuilder/controller.dart 同上,不再重复

视图 lib/pages/state_getBuilder/index.dart

class StateGetBuilderView extends StatelessWidget {
  StateGetBuilderView({Key? key}) : super(key: key);

  final controller = CountController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetBuilder"),
      ),
      body: Center(
        child: Column(
          children: [
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetBuilder - 1");
                return Text('value -> ${_.count}');
              },
            ),
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetBuilder - 2");
                return Text('value -> ${_.count}');
              },
            ),
            Divider(),

            //
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetBuilder - 3");
                return Column(
                  children: [
                    Text('value -> ${_.count}'),
                    ElevatedButton(
                      onPressed: () {
                        _.add();
                      },
                      child: Text('GetBuilder -> add'),
                    )
                  ],
                );
              },
            ),
            Divider(),

            // count2
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetBuilder - 4");
                return Text('value count2 -> ${_.count2}');
              },
            ),
            Divider(),

            // id2
            GetBuilder<CountController>(
              id: "id2",
              init: controller,
              initState: (_) {},
              builder: (_) {
                print("GetBuilder - 4");
                return Text('id2 -> value count2 -> ${_.count2}');
              },
            ),
            Divider(),

            // 按钮
            ElevatedButton(
              onPressed: () {
                controller.add();
              },
              child: Text('add'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.add2();
              },
              child: Text('add2'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.update();
              },
              child: Text('controller.update()'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.update(["id2"]);
              },
              child: Text('controller.update(id2)'),
            ),
          ],
        ),
      ),
    );
  }
}

小结

GetX 比起来,多了手动控制更新,有两点需要注意。

controller.update(); 触发更新

id: "id2", 标记哪个 builder ,触发方式 controller.update(["id2"]); ,可传多个 Array 类型。

ValueBuilder

lib/pages/state_valueBuilder/index.dart

class StateValueBuilderView extends StatelessWidget {
  StateValueBuilderView({Key? key}) : super(key: key);

  @overrideWidget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ValueBuilder"),
      ),
      body: Column(
        children: [
          Center(
            child: ValueBuilder<int?>(
              initialValue: 10,
              builder: (value, updateFn) {
                return Column(
                  children: [
                    Text("count -> " + value.toString()),
                    ElevatedButton(
                      onPressed: () {
                        updateFn(value! + 1);
                      },
                      child: Text('ValueBuilder -> add'),
                    )
                  ],
                );
              },
              // builder: (value, updateFn) => Switch(//   value: value,//   onChanged://       updateFn, // same signature! you could use ( newValue ) => updateFn( newValue )// ),// if you need to call something outside the builder method.
              onUpdate: (value) => print("Value updated: $value"),
              onDispose: () => print("Widget unmounted"),
            ),
          ),
        ],
      ),
    );
  }
}

小结:

适合局部的状态管理,很灵活。

防抖、限流

控制器 lib/pages/state_workers/controller.dart

class CountController extends GetxController {
  final _count = 0.obs;
  set count(value) => this._count.value = value;
  get count => this._count.value;

  add() => _count.value++;

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

    // 每次
    ever(_count, (value) {
      print("ever -> " + value.toString());
    });

    // 第一次
    once(_count, (value) {
      print("once -> " + value.toString());
    });

    // 防抖 2 秒内
    debounce(
      _count,
      (value) {
        print("debounce -> " + value.toString());
      },
      time: Duration(seconds: 2),
    );

    // 定时器 1 秒
    interval(
      _count,
      (value) {
        print("interval -> " + value.toString());
      },
      time: Duration(seconds: 1),
    );
  }
}

视图 lib/pages/state_workers/index.dart

class StateWorkersView extends StatelessWidget {
  StateWorkersView({Key? key}) : super(key: key);

  final controller = CountController();

  @overrideWidget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GetBuilder"),
      ),
      body: Center(
        child: Column(
          children: [
            // 显示GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (_) {
                return Text('value -> ${_.count}');
              },
            ),

            // 按钮ElevatedButton(
              onPressed: () {
                controller.add();
              },
              child: Text('add'),
            ),
          ],
        ),
      ),
    );
  }
}

小结:

ever 适合做监听、日志收集

debounce 适合做搜索输入框

1/0