GetX状态管理最佳实践

总结GetX在实际项目中的使用经验,包括Controller生命周期管理、依赖注入等核心概念。

为什么选择GetX

Flutter状态管理方案众多,我在「回留」项目中选择GetX的原因:

项目结构组织

推荐按功能模块组织代码:

lib/
├── app/
│   ├── modules/
│   │   ├── home/
│   │   │   ├── bindings/
│   │   │   │   └── home_binding.dart
│   │   │   ├── controllers/
│   │   │   │   └── home_controller.dart
│   │   │   └── views/
│   │   │       └── home_view.dart
│   │   ├── photo/
│   │   │   ├── bindings/
│   │   │   ├── controllers/
│   │   │   └── views/
│   ├── routes/
│   │   ├── app_pages.dart
│   │   └── app_routes.dart
│   └── services/
│       └── storage_service.dart

Controller生命周期管理

GetX的Controller有完整的生命周期,合理使用可以避免内存泄漏:

class PhotoController extends GetxController {
  final photos = <Photo>[].obs;
  
  @override
  void onInit() {
    super.onInit();
    // 初始化时调用,适合加载数据
    loadPhotos();
  }
  
  @override
  void onReady() {
    super.onReady();
    // Widget渲染完成后调用
    // 适合执行需要context的操作
  }
  
  @override
  void onClose() {
    // 控制器销毁时调用
    // 清理资源、取消订阅等
    super.onClose();
  }
  
  Future<void> loadPhotos() async {
    // 加载照片逻辑
  }
}

依赖注入的正确姿势

使用Binding统一管理依赖注入:

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    // 懒加载,使用时才创建
    Get.lazyPut<HomeController>(() => HomeController());
    
    // 立即创建
    // Get.put(HomeController());
    
    // 每次获取都创建新实例
    // Get.create<HomeController>(() => HomeController());
  }
}

在路由中绑定:

GetPage(
  name: Routes.HOME,
  page: () => const HomeView(),
  binding: HomeBinding(),
),

响应式状态 vs 简单状态

GetX提供两种状态管理方式:

响应式状态(.obs)

class CounterController extends GetxController {
  final count = 0.obs;
  
  void increment() => count.value++;
}

// 在View中使用
Obx(() => Text('${controller.count.value}'))

简单状态(update)

class CounterController extends GetxController {
  int count = 0;
  
  void increment() {
    count++;
    update(); // 手动触发更新
  }
}

// 在View中使用
GetBuilder<CounterController>(
  builder: (c) => Text('${c.count}'),
)

选择建议:

Worker的妙用

Worker可以监听响应式变量的变化:

class SearchController extends GetxController {
  final searchText = ''.obs;
  final results = <String>[].obs;
  
  @override
  void onInit() {
    super.onInit();
    
    // 防抖:停止输入500ms后执行搜索
    debounce(
      searchText,
      (_) => performSearch(),
      time: const Duration(milliseconds: 500),
    );
    
    // 其他Worker类型
    // ever() - 每次变化都执行
    // once() - 只执行一次
    // interval() - 固定间隔执行
  }
  
  void performSearch() {
    // 执行搜索
  }
}

全局服务的管理

对于需要全局访问的服务,在应用启动时初始化:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化全局服务
  await initServices();
  
  runApp(const MyApp());
}

Future<void> initServices() async {
  // 永久存在的服务
  Get.put(StorageService(), permanent: true);
  Get.put(AuthService(), permanent: true);
  
  // 等待异步初始化完成
  await Get.find<StorageService>().init();
}

常见问题与解决

1. Controller被意外销毁

使用Get.find()时,确保Controller已经注入:

// 安全的获取方式
final controller = Get.find<HomeController>();

// 或者使用isRegistered检查
if (Get.isRegistered<HomeController>()) {
  final controller = Get.find<HomeController>();
}

2. 页面返回后状态丢失

如果需要保持状态,使用permanent参数:

Get.put(MyController(), permanent: true);

3. 内存泄漏

确保在onClose中清理资源:

@override
void onClose() {
  scrollController.dispose();
  subscription?.cancel();
  super.onClose();
}

总结

GetX是一个功能强大且易用的Flutter框架,合理使用可以大大提升开发效率。关键是理解其生命周期管理和依赖注入机制,避免常见的坑。