GetX 在 Flutter 中的使用:两种视图管理方式解析

230 阅读3分钟

GetX 是 Flutter 生态中一个非常流行的微框架。它把状态管理、依赖注入和路由管理打包在一起,用极少的样板代码换取开发效率。但 GetX 内部其实提供了两套截然不同的状态管理方案,很多开发者用了一段时间后依然分不清该用哪个。

这篇文章会先讲清楚 GetX 的基本使用流程,然后重点拆解 GetBuilder 和 Obx 这两种状态管理方式的本质区别,帮你建立清晰的选择标准。

为什么 GetX 能减少样板代码?

传统的 Flutter 状态管理通常依赖 ChangeNotifier,当状态改变时需要调用 notifyListeners(),然后所有监听该 ChangeNotifier 的 Widget 都会重建。在中等规模以上的应用中,这会造成大量不必要的重建。

GetX 的设计目标很明确:只重建真正需要更新的 Widget。它不使用 ChangeNotifier,而是通过自建的 GetStream 和 GetValue 来实现更低延迟的响应式更新。

入门只需要三步:

dart

// 1. 把 MaterialApp 换成 GetMaterialApp(如果要用路由管理)
void main() => runApp(GetMaterialApp(home: Home()));

// 2. 创建 Controller
class CounterController extends GetxController {
  var count = 0.obs;  // .obs 使其变为可观察变量
  void increment() => count++;
}

// 3. 在 Widget 中使用
class Home extends StatelessWidget {
  final controller = Get.put(CounterController());

  @override
  Widget build(context) {
    return Scaffold(
      body: Center(
        child: Obx(() => Text("${controller.count}")),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
      ),
    );
  }
}

这就是 GetX 的“计数器 Plus 版”——比 Flutter 默认的计数器项目少了约 70% 的代码。

GetX 的两种状态管理方式

GetX 官方文档明确说明它有两个不同的状态管理器:简单状态管理器(GetBuilder) 和响应式状态管理器(GetX/Obx) 。

方式一:GetBuilder —— 手动触发的块状更新

GetBuilder 的设计哲学接近 setState()。你在 Controller 中修改数据后,需要显式调用 update()  来通知 UI 刷新。

dart

class CounterController extends GetxController {
  int count = 0;  // 普通变量,不需要 .obs

  void increment() {
    count++;
    update();  // 手动通知更新
  }
}

// UI 中使用
GetBuilder<CounterController>(
  init: CounterController(),
  builder: (controller) => Text("${controller.count}"),
)

特点:更新是“块状”的——整个 GetBuilder 内部的 Widget 都会重建。这种方式的 CPU 开销极小,因为它只做一件事:在需要时重建指定区域。

方式二:Obx —— 自动追踪的细粒度更新

Obx 是响应式状态管理的入口。你只需要在变量后加 .obs,把它放进 Obx 的构建函数中,GetX 就会自动追踪变量变化,只重建真正依赖该变量的那部分 UI。

dart

class CounterController extends GetxController {
  var count = 0.obs;  // 响应式变量

  void increment() => count++;  // 不需要 update(),自动触发
}

// UI 中使用
Obx(() => Text("${controller.count}"))

特点:如果一个 Obx 中引用了多个 .obs 变量,任何一个变化都会触发该 Obx 重建。但 GetX 会做值比较——如果你把 "John" 重新赋值为 "John",它不会触发重建,因为值没有真正改变。

两种方式的核心差异对比

维度GetBuilderObx
变量声明普通变量.obs 后缀
更新触发手动 update()自动响应
重建粒度整个 GetBuilder 区域仅依赖该变量的 Widget
性能开销极低(简单重建)有流式开销,但更精准
适用场景状态变化频繁、需要批量更新的局部区域需要细粒度控制、跨组件共享的状态
内存占用更低略高(维护流订阅)

选择标准其实很简单:如果你的所有变量都会在同一个操作中一起改变,用 GetBuilder,它更简单、开销更小。如果你需要对“谁改了什么”有精细的控制,或者多个 Widget 各自依赖不同的状态片段,用 Obx。

响应式变量的进阶用法

.obs 不仅能作用于基础类型,任何 Dart 对象都可以变成响应式:

dart

var user = User().obs;          // 自定义类
var items = <String>[].obs;     // 列表
var map = <String, int>{}.obs;  // 映射

对于非基础类型,修改后需要调用 .refresh() 或使用 assignAll() 等方法,或者直接重新赋值:

dart

// 列表操作后刷新
items.add("new");
items.refresh();

// 或者直接赋值(推荐)
items.assignAll(["a", "b"]);

GetX 还提供了一系列数据变化监听器,用于在状态改变时执行副作用:

dart

// 每次 count 变化时打印
ever(count, (val) => print("count: $val"));

// 只在第一次变化时触发
once(count, (val) => print("首次变化"));

// 防抖:停止输入 1 秒后才触发
debounce(count, (val) => search(val), time: Duration(seconds: 1));

// 忽略间隔内的多次变化,只触发最后一次
interval(count, (val) => print("最后一次"), time: Duration(seconds: 1));

依赖注入与 GetView

GetX 的依赖注入让 Controller 的获取变得非常简单。Get.put() 注册一个实例,Get.find() 在任何地方取用——不需要传递 context。

如果你不想在每个 View 中都写 Get.put(),可以继承 GetView<Controller>:

dart

class HomeView extends GetView<CounterController> {
  @override
  Widget build(BuildContext context) {
    // controller 属性已经自动就绪
    return Obx(() => Text("${controller.count}"));
  }
}

GetView 本质上是一个带 controller 属性的 StatelessWidget,把依赖获取的逻辑从视图层隐式抽走了。

路由管理:Get.to 与命名路由

GetX 的路由管理最直观的优势是不需要 context:

dart

Get.to(DetailPage());           // 普通跳转
Get.toNamed("/detail");         // 命名路由
Get.off(LoginPage());           // 跳转并移除当前页
Get.offAll(LoginPage());        // 跳转并清空栈
Get.back();                     // 返回

命名路由需要配置 GetPage:

dart

GetMaterialApp(
  initialRoute: '/',
  getPages: [
    GetPage(name: '/', page: () => HomePage()),
    GetPage(name: '/detail/:id', page: () => DetailPage()),
  ],
)

动态路由参数通过 Get.parameters 获取,复杂对象则通过 arguments 传递。