GetX 是 Flutter 生态中一个非常流行的微框架。它把状态管理、依赖注入和路由管理打包在一起,用极少的样板代码换取开发效率。但 GetX 内部其实提供了两套截然不同的状态管理方案,很多开发者用了一段时间后依然分不清该用哪个。
这篇文章会先讲清楚 GetX 的基本使用流程,然后重点拆解 GetBuilder 和 Obx 这两种状态管理方式的本质区别,帮你建立清晰的选择标准。
为什么 GetX 能减少样板代码?
传统的 Flutter 状态管理通常依赖 ChangeNotifier,当状态改变时需要调用 notifyListeners(),然后所有监听该 ChangeNotifier 的 Widget 都会重建。在中等规模以上的应用中,这会造成大量不必要的重建。
GetX 的设计目标很明确:只重建真正需要更新的 Widget 。它不使用 ChangeNotifier,而是通过自建的 GetStream 和 GetValue 来实现更低延迟的响应式更新。
入门只需要三步:
dart
scala
// 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
scala
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
scala
class CounterController extends GetxController {
var count = 0.obs; // 响应式变量
void increment() => count++; // 不需要 update(),自动触发
}
// UI 中使用
Obx(() => Text("${controller.count}"))
特点 :如果一个 Obx 中引用了多个 .obs 变量,任何一个变化都会触发该 Obx 重建。但 GetX 会做值比较------如果你把 "John" 重新赋值为 "John",它不会触发重建,因为值没有真正改变。
两种方式的核心差异对比
| 维度 | GetBuilder | Obx |
|---|---|---|
| 变量声明 | 普通变量 | .obs 后缀 |
| 更新触发 | 手动 update() |
自动响应 |
| 重建粒度 | 整个 GetBuilder 区域 |
仅依赖该变量的 Widget |
| 性能开销 | 极低(简单重建) | 有流式开销,但更精准 |
| 适用场景 | 状态变化频繁、需要批量更新的局部区域 | 需要细粒度控制、跨组件共享的状态 |
| 内存占用 | 更低 | 略高(维护流订阅) |
选择标准其实很简单 :如果你的所有变量都会在同一个操作中一起改变,用 GetBuilder,它更简单、开销更小。如果你需要对"谁改了什么"有精细的控制,或者多个 Widget 各自依赖不同的状态片段,用 Obx。
响应式变量的进阶用法
.obs 不仅能作用于基础类型,任何 Dart 对象都可以变成响应式:
dart
ini
var user = User().obs; // 自定义类
var items = <String>[].obs; // 列表
var map = <String, int>{}.obs; // 映射
对于非基础类型,修改后需要调用 .refresh() 或使用 assignAll() 等方法,或者直接重新赋值:
dart
csharp
// 列表操作后刷新
items.add("new");
items.refresh();
// 或者直接赋值(推荐)
items.assignAll(["a", "b"]);
GetX 还提供了一系列数据变化监听器,用于在状态改变时执行副作用:
dart
scss
// 每次 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
scala
class HomeView extends GetView<CounterController> {
@override
Widget build(BuildContext context) {
// controller 属性已经自动就绪
return Obx(() => Text("${controller.count}"));
}
}
GetView 本质上是一个带 controller 属性的 StatelessWidget,把依赖获取的逻辑从视图层隐式抽走了。
路由管理:Get.to 与命名路由
GetX 的路由管理最直观的优势是不需要 context:
dart
scss
Get.to(DetailPage()); // 普通跳转
Get.toNamed("/detail"); // 命名路由
Get.off(LoginPage()); // 跳转并移除当前页
Get.offAll(LoginPage()); // 跳转并清空栈
Get.back(); // 返回
命名路由需要配置 GetPage:
dart
less
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => HomePage()),
GetPage(name: '/detail/:id', page: () => DetailPage()),
],
)