Flutter笔记:状态提升、控制器模式、GetX控制器和服务

举报
jcLee95 发表于 2023/11/13 21:05:53 2023/11/13
【摘要】 本文聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。
Flutter笔记
状态提升、控制器模式、GetX控制器和服务


【简介】本文聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。



在Flutter中,状态管理是一个重要的主题,它涉及到如何存储和更新应用的数据以及如何在组件之间共享数据。状态管理的方法有很多种,包括状态提升、控制器模式、响应式编程等。每种方法都有其优点和适用场景。

状态提升是一种简单的状态管理方法,它通过将状态放在组件树的上层来实现状态的共享。但是,状态提升可能会导致组件树过于复杂,而且不适用于全局状态的管理。

控制器模式是一种更加灵活的状态管理方法,它通过将状态封装在控制器对象中,然后通过控制器来管理状态。控制器模式可以有效地管理全局状态,而且可以避免组件树过于复杂。

响应式编程是一种基于数据流的编程模式,它可以使状态的更新变得更加直观和易于理解。在响应式编程中,状态被视为数据流,组件可以监听数据流的变化并根据变化来更新自己。

GetX库提供了一种简单而强大的状态管理方法,它结合了控制器模式和响应式编程的优点。在GetX中,你可以创建一个继承自GetxController或GetxService的类来保存状态,然后在状态改变时调用update()方法或者使用.obs来通知所有监听这个状态的组件。

接下来,本文具体聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。



状态提升(State Lifting) 是一种在 Flutter 中常用的状态管理模式,其基本思想是将状态放在需要这个状态的最小公共祖先组件上。这样,所有需要这个状态的子组件都可以通过祖先组件来访问和修改这个状态。例如,如果两个兄弟组件都需要访问和修改同一个状态,那么这个状态就应该放在它们的父组件上。

详细说来,这种模式通常用于处理以下情况:

  • 共享数据: 当多个组件需要访问和共享相同的数据时,将状态提升到这些组件的共同祖先组件中,以便它们可以共享数据。

  • 状态同步: 当某个状态需要被多个组件 修改 时,将这个状态提升到共同的父组件,由父组件负责管理和更新状态,然后将状态传递给子组件。


虽然状态提升模式在一些简单的场景下工作得很好,但是它也有一些缺陷。

由于每个组件都可以有自己的 内部状态(即局部状态),但当多个组件之间需要共享状态或协同工作时,状态提升就变得非常有用。但是在很多实际开发场景中并不是说你想提升状态就可以提升状态。显而易见的是,如果我们封装一个第三方组件库,不可能在组件发布后去库的使用者的代码里提升状态,但是使用者又有可能需要用到这些状态来控制我们所封装的组件,因此这种情况下状态提升并不是可行的解决方案。

另外一个方面,状态提升在 Flutter 中将很容易导致扩大刷新范围,浪费性能。因此需要一种有效的解决方案来弥补状态提升的不足。这个解决方案就是所谓的 控制器模式。



顾名思义,ChangeNotifier(改变通知) 可以在状态改变时通知其监听器,是一个可以混入到类中的类。你可以创建一个继承自 ChangeNotifier 的类来保存状态,然后在状态改变时调用 notifyListeners() 方法来通知所有监听这个状态的组件

其中需要指出的是,ChangeNotifier 实现了 Listenable 接口,用于提供一个可以发送变化通知的对象:

  • Listenable 接口定义了两个方法:addListener()  removeListener(),这两个方法分别用于添加和移除监听器。任何实现了 Listenable 接口的对象都可以被其他对象监听,当 Listenable 对象的状态发生变化时,它可以通知所有的监听器。(实际上是 发布-订阅模式-见《发布订阅模式原理及其应用》,地址:https://jclee95.blog.csdn.net/article/details/129930814

  • ChangeNotifier  Listenable 的一个具体实现,它提供了一个 notifyListeners() 方法,可以在状态改变时调用,以通知所有的监听器。ChangeNotifier 内部维护了一个监听器列表,当你调用 addListener() 方法时,监听器会被添加到这个列表中;当你调用removeListener() 方法时,监听器会从这个列表中移除。

例如,你可以创建一个 Counter 类,它继承自 ChangeNotifier,并有一个 count 状态和一个 increment 方法:

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

然后,你可以使用 ListenableBuilder  监听 这个 Counter 对象。ListenableBuilder 函数会在每次 ChangeNotifier 调用 notifyListeners() 以实现 Counter 对象的 状态改变 时被调用。

class CounterWidget extends StatelessWidget {
  final Counter counter;

  CounterWidget({@required this.counter});

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: counter,
      builder: (context, _) {
        return Text('Count: ${counter.count}');
      },
    );
  }
}

其中:

  1. 当你创建一个 ListenableBuilder 并传入一个 Listenable 对象(ChangeNotifier  Listenable 的实现)时,ListenableBuilder 会将自己添加到 Listenable 的监听器列表中。

  2.  Listenable 对象的状态改变并调用 notifyListeners() 方法时,所有的监听器(包括ListenableBuilder)都会收到通知。

  3.  ListenableBuilder 收到通知时,它会调用其builder函数来重建子组件。builder函数会接收到当前的 BuildContextListenable 对象,以及一个可选的 child 参数,然后返回一个新的 Widget

  4. ListenableBuilder 会将 builder 函数返回的 新 Widget 显示在屏幕上,从而更新UI的效果。

功能上 ListenableBuilder  AnimationBuilder 是一样的。

因此在封装组件时,经常使用控制器来命名这个基于 改变通知(发布订阅) 的类:

/// 计数器控制器类
/// 
/// - 存储计数器状态;
/// - 提供改变状态的方法作为外部改变状态的接口。
class CounterController with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

控制器模式是一种更加灵活的状态管理模式。在这种模式下,状态被保存在一个或多个控制器对象中,而不是直接保存在组件中。组件可以创建和管理这些控制器对象,也可以通过它们来访问和修改状态。

当状态改变时,控制器会通知所有监听这个状态的组件,这样这些组件就可以根据新的状态来更新自己。因为状态被保存在控制器中,所以它可以被任何可以访问到这个控制器的组件共享,这使得状态管理变得更加灵活和高效。

状态提升时,我们仅仅时把状态放在了层级更高的组件,但是控制器模式将状态放在一个独立的类中,这个类不仅用于存储状态,也提供相应的改变方法。实际上,上一节混入了 ChangeNotifier  Counter 类就是一个控制器。

为什么控制器要混入或继承于ChangeNotifier?

因为我们的目标是状态改变后能够及时的更新UI。

 Flutter 中,提供 ChangeNotifier - ListenableBuilder 机制:

前者用于控制器类——因为控制器类是状态改变的源:

  • 所有改变状态变量的操作被封装在控制器类中,以接口的形式暴露给外部使用;
  • 在修改的接口方法中,每当数据更改数据后调用 notifyListeners()方法完成通知监听器。

后者用于 build 方法的某个局部需要依赖于数据更新的UI中:

  • ListenableBuilder 的 builder 方法在监听器被通知后使用新的数据进行重构;


 GetX 库中,你可以创建一个继承自 GetxController 的类来保存状态,然后在状态改变时调用 update() 方法来通知所有监听这个状态的组件。

class CounterController extends GetxController {
  int count = 0;

  void increment() {
    count++;
    update();
  }
}

然后,你可以在UI类中使用 GetBuilder 来监听这个 CounterController 对象。当 CounterController 对象的状态改变时,GetBuilder 会自动重建,从而更新UI。

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterController = Get.put(CounterController());

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: GetBuilder<CounterController>(
          builder: (controller) => Text('Count: ${controller.count}'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: counterController.increment,
      ),
    );
  }
}

仅仅从使用的角度上看:

  1. 在控制器类中:
  • 从继承于 ChangeNotifier 变成了继承于 GetxController
  • 从使用 notifyListeners(); 函数通知更新UI变成了 使用 update(); 函数更新UI。
  1. 在 UI 的build函数的需要更新处:
  • 从使用 ListenableBuilder 类包括需要依赖数据更新部分改成了使用 GetBuilder 类。

GetX的响应式状态管理提供了一种用起来更加方便地方式——不再需要使用 update() 方法。但是这意味着需要在每一个变量上做些手脚——添加.obs 变成“响应式变量”。

在GetX库内部,.obs 是一个扩展方法,它可以用于将普通的Dart值(如String、int、double等)转换为可观察的 Rx 对象。——这是因为在 Dart 语言中,你可以通过 扩展(extension) 语法来为已有的类型添加新的方法或属性。GetX库就使用了这个特性,为Dart的基本类型添加了 .obs 扩展方法。

所以,当你在一个String、int、double等值后面调用.obs时,你实际上是在创建一个新的Rx对象,这个对象的初始值就是这个值。例如,var count = 0.obs;就等价于var count = Rx(0);。

Rx对象是可观察的,你可以使用value属性来获取或设置它的值,也可以使用addListener()方法来添加监听器。当Rx对象的值改变时,所有的监听器都会收到通知。这就是GetX的响应式状态管理的基础。

基于响应式转台管理,控制器类调整为:

// 控制器类
class CounterController extends GetxController {
  RxInt count = 0.obs;

  void increment() {
    count++;
  }
}

而在UI部分,也不再使用 ChangeNotifier ListenableBuilder或者简单状态管理的GetBuilder ,而是由 Obx进行包裹,例如:

// 界面组件
class CounterView extends StatelessWidget {
  final CounterController controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter App with GetX'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => Text('Count: ${controller.count}')),
            ElevatedButton(
              onPressed: () => controller.increment(),
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

实际上 GetxService 和 的功能是很像的,我们可以将控制器类改为一个服务类,比如计数器的例子:

class CounterService extends GetxService {
  var count = 0.obs;

  void increment() {
    count.value++;
  }
}

然后,你可以在UI类中使用Obx来监听这个CounterService对象。当CounterService对象的状态改变时,Obx会自动重建,从而更新UI:

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterService = Get.put(CounterService());

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${counterService.count.value}')),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: counterService.increment,
      ),
    );
  }
}

从使用上看没有什么不同。但是GetxService是一个长生命周期的类,一旦被创建,就会一直存在,直到应用被关闭或者你手动调用Get.reset()。


GetxController  GetxService 都是 GetX 库中的核心组件,它们都有生命周期方法(onInit(), onReady(), onClose()),并且都可以用于 管理状态和依赖。但是,它们的主要区别在于它们的存活时间和用途不一样。


GetxController 是一个用于状态管理的类,它的实例可以通过 Get.put() Get.lazyPut() Get.putAsync() Get.create() 等方法创建并绑定到一个生命周期。当与绑定的页面不再需要时, GetX会自动删除GetxController的实例 以释放内存。因此,GetxController通常用于页面和小部件的 局部状态管理,例如用户界面的交互、表单状态、主题颜色等。


GetxService 是一个长期存活在应用中的类,它的实例一旦被创建,就不会被自动删除,除非你手动调用Get.reset()。因此,GetxService 通常用于需要全局访问和长期存在的服务,例如用户认证、数据库操作、网络请求等。

最常见的就是认证和权限,者往往是整个应用生命周期都需要的,因此我们经常定义各异认证服务,用于处理应用中与认证相关的状态:

class AuthService extends GetxService {
  Future<AuthService> init() async {
    // Initialize your class
  }
}

在GetX中,Get.reset()方法用于清除所有的依赖项,包括GetxController和GetxService的实例。这个方法通常在你需要完全重置应用状态时使用,例如用户注销登录时。

然而,如果你有一个场景需要重置CounterService的状态,例如用户注销登录时,你可以调用Get.reset()。例如:

void userLogout() {
  // ...其他的注销逻辑...

  // 重置应用状态
  Get.reset();
}

需要指出的是,Get.reset()会清除所有的依赖项,包括所有的GetxController和GetxService的实例。如果你只想重置CounterService的状态,你可以在CounterService中添加一个重置状态的方法,然后在需要的地方调用这个方法。比如:

void userLogout() {
  // ...其他的注销逻辑...

  // 重置CounterService的状态
  Get.find<CounterService>().reset();
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。