Skip to content

GetX

GetX 是一个多功能框架,它把状态管理、路由导航、依赖注入三大功能打包在一起,主打一个字——。代码量极少,上手极简,适合快速出活。

GetX 的名字来自 Get——一个单词搞定状态、路由、依赖,少写代码多干活。

为什么要学 GetX?

|| 其他方案 | GetX 的不同 | |---------|-----------| | 只做状态管理 | 状态管理 + 路由 + 依赖注入,一包搞定 | | 需要 BuildContext | 不依赖 BuildContext,随处可用 | | 样板代码多 | 响应式写法极简,.obs 一行搞定 | | 需要顶层包裹 | 部分用法无需顶层配置 |

适合谁?

GetX 学习成本最低,适合想快速出原型小型项目的开发者。但它的设计理念与 Flutter 官方推荐有所不同,在大型项目或团队协作中需要谨慎评估。建议先学 ProviderRiverpod 理解 Flutter 状态管理的基本思路,再来看 GetX。

版本说明

本文基于 get ^4.6.6 编写。GetX 4.x 是当前主流版本,API 相对稳定。

安装

bash
flutter pub add get

安装后,需要将 MaterialApp 替换为 GetMaterialApp,以启用 GetX 的路由和依赖注入功能:

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(
    // ─── ★ GetMaterialApp ──────────────
    const GetMaterialApp(
      home: MyApp(),
    ),
    // ─── ☆ GetMaterialApp ──────────────
  );
}

TIP

如果只用响应式状态管理(.obs + Obx),不使用 GetX 的路由和依赖注入,可以不替换 MaterialApp。但推荐使用 GetMaterialApp 以获得完整功能。

两种响应式方式

GetX 提供两种响应式状态管理方式,各有适用场景:

|| 方式 | 核心思路 | 适用场景 | |------|--------|---------| | 响应式(.obs | 数据变了自动刷新 | 简单状态,需要精细更新 | | 简单状态(GetBuilder | 手动调用 update() 刷新 | 不需要自动监听,手动控制更新 |

下面逐一讲解。


一、响应式状态管理(.obs)

这是 GetX 最具特色的方式——用 .obs 把普通变量变成「可观察的」,UI 会自动响应变化。

快速上手:计数器

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const GetMaterialApp(home: CounterPage()));
}

// ─── ★ 定义控制器 ──────────────
class CounterController extends GetxController {
  final count = 0.obs;  // .obs 把 int 变成可观察的响应式变量

  void increment() => count++;  // 修改后自动通知 UI
}
// ─── ☆ 定义控制器 ──────────────

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  // 通过 Get.find() 或 Get.put() 获取控制器
  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('GetX 计数器')),
      body: Center(
        // ─── ★ Obx 监听变化 ──────────────
        child: Obx(() => Text(
          '计数: ${controller.count.value}',
          style: const TextStyle(fontSize: 48),
        )),
        // ─── ☆ Obx 监听变化 ──────────────
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

.obs 的类型

.obs 可以用在多种基本类型和集合上:

dart
// 基本类型
final name = ''.obs;          // RxString
final count = 0.obs;          // RxInt
final price = 0.0.obs;        // RxDouble
final isActive = false.obs;   // RxBool

// 集合类型
final items = <String>[].obs;  // RxList<String>
final user = <String, dynamic>{}.obs;  // RxMap<String, dynamic>

// 自定义对象
final user = User(name: '张三', age: 25).obs;  // Rx<User>

读取和修改值

dart
// 读取值:用 .value
print(count.value);   // 0
print(name.value);    // '张三'

// 修改值
count.value = 10;          // 直接赋值
name.value = '李四';       // 直接赋值

// RxInt 支持直接运算(无需 .value)
count++;           // 等价于 count.value++
count += 5;        // 等价于 count.value += 5

// RxList / RxMap 支持直接操作
items.add('苹果');          // 直接 add,不需要 .value
items.removeAt(0);          // 直接操作
user['name'] = '王五';      // 直接赋值

自定义对象必须用 .value 修改

对于自定义对象(如 Rx<User>),必须通过 .value 重新赋值,不能只修改对象内部的属性:

dart
// ❌ 错误:修改属性不会触发更新
final user = User(name: '张三', age: 25).obs;
user.value.name = '李四';  // UI 不会更新!

// ✅ 正确:创建新对象赋值
user.value = User(name: '李四', age: 25);  // UI 会更新

// ✅ 正确:使用 update 方法(推荐)
user.update((val) {
  val!.name = '李四';  // update 内部会自动触发通知
});

Obx — 自动响应 UI

Obx 是 GetX 的响应式 Widget,它会自动追踪内部使用的 .obs 变量,变量变化时自动重建:

dart
Obx(() => Text('计数: ${controller.count.value}'))

Obx 的工作原理

  1. Obx 执行内部的 builder 函数
  2. 执行过程中,所有被读取的 .obs 变量都会被自动记录
  3. 当任何一个 .obs 变量变化时,Obx 自动重新执行 builder
  4. 不需要手动订阅或取消订阅

二、简单状态管理(GetBuilder)

GetBuilder 是 GetX 的另一种方式——不会自动监听,你需要手动调用 update() 来触发刷新。

适用场景

场景推荐方式
需要自动响应变化.obs + Obx
只在特定时机刷新GetBuilder + update()
状态变化不频繁GetBuilder(性能更好)
不需要响应式特性GetBuilder

基本用法

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class CounterController extends GetxController {
  int count = 0;  // 普通 int,不是 .obs

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

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  final controller = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('GetBuilder 计数器')),
      body: Center(
        // ─── ★ GetBuilder 监听 ──────────────
        child: GetBuilder<CounterController>(
          builder: (ctrl) => Text(
            '计数: ${ctrl.count}',
            style: const TextStyle(fontSize: 48),
          ),
        ),
        // ─── ☆ GetBuilder 监听 ──────────────
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

update() 的条件刷新

update() 可以传入 ID,只刷新特定的 GetBuilder

dart
class UserController extends GetxController {
  String name = '张三';
  int age = 25;

  void updateName(String newName) {
    name = newName;
    update(['name']);  // 只刷新监听 'name' 的 GetBuilder
  }

  void updateAge(int newAge) {
    age = newAge;
    update(['age']);   // 只刷新监听 'age' 的 GetBuilder
  }
}

// 在 Widget 中指定监听的 ID
GetBuilder<UserController>(
  id: 'name',  // 👈 只响应 update(['name'])
  builder: (ctrl) => Text('姓名: ${ctrl.name}'),
),

GetBuilder<UserController>(
  id: 'age',   // 👈 只响应 update(['age'])
  builder: (ctrl) => Text('年龄: ${ctrl.age}'),
),

三、Obx vs GetBuilder 选择指南

|| | Obx | GetBuilder | |---|---------|-----------| | 触发方式 | .obs 变量变化自动触发 | 手动调用 update() | | 数据类型 | 必须用 .obs 包装 | 普通变量即可 | | 重建粒度 | 精确到单个 .obs 变量 | 整个 GetBuilder 区域 | | 性能 | 略低(响应式有额外开销) | 略高(无自动监听开销) | | 适用 | 数据频繁变化、需要自动响应 | 不频繁变化、手动控制刷新时机 |

一句话选择

数据经常变、想让 UI 自动跟着变Obx想自己控制什么时候刷新GetBuilder


四、GetxController — 控制器生命周期

GetxController 是 GetX 状态管理的核心,它承载业务逻辑和状态数据。

生命周期方法

dart
class MyController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    // 控制器初始化时调用(类似 initState)
    print('控制器初始化');
  }

  @override
  void onReady() {
    super.onReady();
    // 在 onInit 之后,Widget 渲染完成后调用
    // 适合做页面加载后的数据请求
    print('页面已渲染完成');
  }

  @override
  void onClose() {
    // 控制器被销毁时调用(类似 dispose)
    // 释放资源、取消订阅等
    print('控制器被销毁');
    super.onClose();
  }
}

生命周期流程:

创建控制器 → onInit() → onReady() → [正常使用] → onClose() → 销毁

获取控制器的方式

dart
// 1️⃣ Get.put — 立即创建并注册(最常用)
final controller = Get.put(CounterController());

// 2️⃣ Get.lazyPut — 懒加载,首次使用时才创建
Get.lazyPut<CounterController>(() => CounterController());

// 3️⃣ Get.find — 获取已注册的控制器
final controller = Get.find<CounterController>();

// 4️⃣ Get.create — 每次获取都创建新实例
Get.create<CounterController>(() => CounterController());

|| 方式 | 何时创建 | 实例数量 | 适用场景 | |------|---------|---------|---------| | Get.put | 立即 | 单例 | 大多数场景 | | Get.lazyPut | 首次 find 时 | 单例 | 不确定是否会用到 | | Get.find | 不创建,只查找 | — | 获取已注册的控制器 | | Get.create | 每次获取时 | 多实例 | 需要独立实例的场景 |


五、依赖注入与控制器绑定

全局注册

GetMaterialApp 之前注册,全局可用:

dart
void main() {
  Get.put(UserController());
  Get.put(CartController());
  runApp(const GetMaterialApp(home: MyApp()));
}

页面级绑定(推荐)

使用 Bindings 让控制器与页面绑定——页面打开时创建,页面关闭时销毁:

dart
// 1️⃣ 定义 Binding
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<HomeController>(() => HomeController());
  }
}

// 2️⃣ 在路由中绑定
Get.to(() => const HomePage(), binding: HomeBinding());

TIP

页面级绑定是推荐做法,可以避免控制器常驻内存,页面关闭时自动释放。

使用 GetView 简化代码

如果一个页面只使用一个控制器,可以用 GetView 代替 StatelessWidget,省去手动获取控制器的步骤:

dart
// ─── ★ GetView 简化 ──────────────
class HomePage extends GetView<HomeController> {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    // 直接用 controller,不需要 Get.put 或 Get.find
    return Obx(() => Text('计数: ${controller.count.value}'));
  }
}
// ─── ☆ GetView 简化 ──────────────

GetView 内部自动调用了 Get.find<T>(),所以需要确保控制器已被注册(通过 Get.putBindings)。

使用 GetWidget(不常用)

GetWidgetGetView 类似,区别是它会缓存控制器实例,不会随 Widget 重建而重新获取。适合控制器需要跨 Widget 共享的场景。大多数情况下用 GetView 即可。


六、GetX 路由导航

GetX 自带路由管理,不依赖 BuildContext,随处可用。

基本导航

dart
// 跳转到新页面
Get.to(() => const SecondPage());

// 带参数跳转
Get.to(() => const DetailPage(), arguments: {'id': 42});

// 在目标页面获取参数
final args = Get.arguments;  // {'id': 42}

// 返回上一页
Get.back();

// 返回并携带结果
Get.back(result: '返回的数据');

// 替换当前页面
Get.off(() => const LoginPage());

// 清空所有页面并跳转(适合登录后跳主页)
Get.offAll(() => const HomePage());

命名路由

dart
// 在 GetMaterialApp 中定义路由
GetMaterialApp(
  getPages: [
    GetPage(name: '/', page: () => const HomePage()),
    GetPage(name: '/detail/:id', page: () => const DetailPage()),
  ],
);

// 使用命名路由跳转
Get.toNamed('/detail/42');

// 获取路径参数
final id = Get.parameters['id'];  // '42'

七、完整示例:待办事项 App

综合前面所学,用 GetX 做一个待办事项应用:

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const GetMaterialApp(home: TodoPage()));
}

// ============ 数据模型 ============

class Todo {
  final String title;
  final bool done;
  const Todo({required this.title, this.done = false});
  Todo copyWith({String? title, bool? done}) =>
      Todo(title: title ?? this.title, done: done ?? this.done);
}

// ============ 状态管理 ============

class TodoController extends GetxController {
  // 使用 .obs 响应式列表
  final todos = <Todo>[].obs;

  // 派生状态:未完成数量(自动响应 todos 变化)
  int get uncompletedCount => todos.where((t) => !t.done).length;

  void add(String title) {
    todos.add(Todo(title: title));
  }

  void toggle(int index) {
    todos[index] = todos[index].copyWith(done: !todos[index].done);
  }

  void remove(int index) {
    todos.removeAt(index);
  }
}

// ============ UI ============

class TodoPage extends StatelessWidget {
  const TodoPage({super.key});

  // 注册控制器
  final controller = Get.put(TodoController());
  final _controller = TextEditingController();

  void _addTodo() {
    final text = _controller.text.trim();
    if (text.isEmpty) return;
    controller.add(text);
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // ─── ★ Obx 监听派生状态 ──────────────
        title: Obx(() => Text('待办事项 (${controller.uncompletedCount} 未完成)')),
        // ─── ☆ Obx 监听派生状态 ──────────────
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: '输入待办事项...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(onPressed: _addTodo, child: const Text('添加')),
              ],
            ),
          ),
          Expanded(
            // ─── ★ Obx 监听列表变化 ──────────────
            child: Obx(() {
              final todos = controller.todos;
              if (todos.isEmpty) {
                return const Center(child: Text('暂无待办事项'));
              }
              return ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];
                  return ListTile(
                    leading: Checkbox(
                      value: todo.done,
                      onChanged: (_) => controller.toggle(index),
                    ),
                    title: Text(
                      todo.title,
                      style: TextStyle(
                        decoration: todo.done
                            ? TextDecoration.lineThrough
                            : TextDecoration.none,
                      ),
                    ),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => controller.remove(index),
                    ),
                  );
                },
              );
            }),
            // ─── ☆ Obx 监听列表变化 ──────────────
          ),
        ],
      ),
    );
  }
}

八、完整示例:购物车(GetBuilder 版)

GetBuilder 实现购物车,展示手动控制刷新的方式:

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const GetMaterialApp(home: CartPage()));
}

// ========== 数据模型 ==========
class Product {
  final String name;
  final int price;
  const Product({required this.name, required this.price});
}

// ========== 状态管理 ==========
class CartController extends GetxController {
  final List<Product> _items = [];
  List<Product> get items => List.unmodifiable(_items);
  int get count => _items.length;
  int get totalPrice => _items.fold<int>(0, (sum, item) => sum + item.price);

  void add(Product product) {
    _items.add(product);
    update();  // 手动通知刷新
  }

  void removeAt(int index) {
    _items.removeAt(index);
    update();
  }

  void clear() {
    _items.clear();
    update();
  }
}

// ========== UI ==========
class CartPage extends StatelessWidget {
  const CartPage({super.key});

  final controller = Get.put(CartController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // ─── ★ GetBuilder 监听 ──────────────
        title: GetBuilder<CartController>(
          builder: (ctrl) => Text('购物车 (${ctrl.count})'),
        ),
        // ─── ☆ GetBuilder 监听 ──────────────
      ),
      body: GetBuilder<CartController>(
        builder: (ctrl) {
          if (ctrl.items.isEmpty) {
            return const Center(child: Text('购物车是空的'));
          }
          return Column(
            children: [
              Expanded(
                child: ListView.builder(
                  itemCount: ctrl.items.length,
                  itemBuilder: (context, index) {
                    final item = ctrl.items[index];
                    return ListTile(
                      title: Text(item.name),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text(${item.price}'),
                          IconButton(
                            icon: const Icon(Icons.delete),
                            onPressed: () => controller.removeAt(index),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(16),
                child: Text(
                  '总计: ¥${ctrl.totalPrice}',
                  style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
              ),
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.add(
          const Product(name: '苹果', price: 5),
        ),
        child: const Icon(Icons.add),
      ),
    );
  }
}

九、GetX 工具方法速查

SnackBar / Dialog / BottomSheet

GetX 提供了不依赖 BuildContext 的弹窗方法:

dart
// SnackBar
Get.snackbar(
  '提示',
  '操作成功',
  snackPosition: SnackPosition.BOTTOM,
);

// Dialog
Get.dialog(
  AlertDialog(
    title: const Text('确认'),
    content: const Text('确定要删除吗?'),
    actions: [
      TextButton(onPressed: () => Get.back(), child: const Text('取消')),
      TextButton(onPressed: () => Get.back(), child: const Text('确定')),
    ],
  ),
);

// BottomSheet
Get.bottomSheet(
  Container(
    padding: const EdgeInsets.all(16),
    child: const Text('底部弹窗内容'),
  ),
);

国际化与主题切换

dart
// 切换主题
Get.changeTheme(ThemeData.dark());
Get.changeTheme(ThemeData.light());

// 国际化
// 需要在 GetMaterialApp 中配置 translations 和 locale
Get.updateLocale(const Locale('zh', 'CN'));

GetX vs Provider vs Riverpod 快速对比

|| 对比项 | Provider | Riverpod | GetX | |-------|----------|----------|------| | 状态管理方式 | ChangeNotifier | Provider / NotifierProvider | .obs / GetBuilder | | 依赖 BuildContext | 是 | 否 | 否 | | 学习成本 | ⭐⭐ | ⭐⭐⭐ | ⭐ | | 样板代码量 | 中等 | 中等 | 极少 | | 额外功能 | 无 | 无 | 路由 + 依赖注入 + 弹窗 | | 编译时安全 | 弱 | 强 | 弱 | | 社区认可度 | 官方推荐 | 社区推荐 | 有争议 |


常见问题

1. Obx 中没有使用 .obs 变量会怎样?

Obx 的 builder 中必须至少使用一个 .obs 变量,否则会报错:

dart
// ❌ 错误:Obx 内没有 .obs 变量
Obx(() => Text('固定文本'))  // 报错!

// ✅ 正确:Obx 内使用了 .obs 变量
Obx(() => Text('计数: ${controller.count.value}'))

// ✅ 如果不需要响应式,直接用普通 Widget
Text('固定文本')

2. 控制器获取报错 "Controller not found"?

确保在使用前已注册控制器:

dart
// ❌ 错误:还没注册就获取
final controller = Get.find<MyController>();  // 报错!

// ✅ 正确:先注册再获取
Get.put(MyController());
final controller = Get.find<MyController>();

3. .obs 自定义对象修改后 UI 不更新?

自定义对象必须整体赋值或使用 update 方法:

dart
// ❌ 错误:只修改属性
final user = User(name: '张三', age: 25).obs;
user.value.name = '李四';  // UI 不会更新

// ✅ 正确方式一:整体赋值
user.value = User(name: '李四', age: 25);

// ✅ 正确方式二:使用 update 方法
user.update((val) {
  val!.name = '李四';
});

4. 页面退出后控制器还在内存中?

如果没有使用 BindingsGet.delete(),控制器会一直存活。推荐使用页面级绑定:

dart
Get.to(() => const DetailPage(), binding: DetailBinding());
// 页面退出后,控制器自动销毁

或者手动删除:

dart
Get.delete<MyController>();

5. Obx 和 GetBuilder 能混用吗?

可以。同一个控制器中,.obs 变量用 Obx 监听,普通变量用 GetBuilder 监听。但不建议对同一变量同时使用两种方式,容易造成混乱。

6. 什么时候该用 GetX,什么时候不该用?

场景推荐
快速原型 / 个人项目 / 小型 App✅ GetX
需要极简代码、快速出活✅ GetX
大型项目 / 团队协作❌ 考虑 Riverpod / Bloc
遵循 Flutter 官方设计理念❌ Provider / Riverpod
需要严格的类型安全❌ Riverpod

下一步

基于 Flutter 官方文档整理