【UI状态管理】sealed class + GetX vs FutureBuilder:相似性与区别

sealed class + GetX vs FutureBuilder:相似性与区别

本文对比 sealed class + GetX 的 UI 渲染方式与 Flutter 的 FutureBuilder,分析它们的相似性和适用场景。


一、相似性

1.1 核心思想相似

两者都是根据异步状态渲染不同 UI

  • FutureBuilder:根据 Future 的状态(未完成、已完成、已出错)渲染 UI
  • sealed class + GetX:根据自定义状态(Loading、Success、Error)渲染 UI

1.2 状态模式相似

两者都遵循状态模式,根据当前状态决定显示什么:

// FutureBuilder 的状态
ConnectionState.none        → 初始状态
ConnectionState.waiting    → 加载中
ConnectionState.active     → 进行中
ConnectionState.done       → 完成(成功或失败)

// sealed class 的状态
Loading()  → 加载中
Success()  → 成功
Error()    → 失败

二、代码对比

2.1 FutureBuilder 实现

class UserPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('用户信息')),
      body: FutureBuilder<String>(
        future: _loadUserInfo(), // 返回 Future<String>
        builder: (context, snapshot) {
          // 根据 snapshot 的状态渲染 UI
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(Icons.error, color: Colors.red, size: 64),
                  const SizedBox(height: 16),
                  Text('出错了:${snapshot.error}'),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: () {
                      // 重新构建 FutureBuilder 需要重建整个 widget
                      setState(() {}); // 但这里是 StatelessWidget,无法用 setState
                    },
                    child: const Text('重试'),
                  ),
                ],
              ),
            );
          }
          
          if (snapshot.hasData) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(Icons.check_circle, color: Colors.green, size: 64),
                  const SizedBox(height: 16),
                  Text('加载成功:${snapshot.data}'),
                ],
              ),
            );
          }
          
          return const SizedBox.shrink();
        },
      ),
    );
  }
  
  Future<String> _loadUserInfo() async {
    await Future.delayed(const Duration(seconds: 1));
    return '这是用户信息:张三,年龄 25';
  }
}

2.2 sealed class + GetX 实现

// 状态定义
sealed class LoadState {}
class Loading extends LoadState {}
class Success extends LoadState {
  final String data;
  Success(this.data);
}
class Error extends LoadState {
  final String message;
  Error(this.message);
}

// Controller
class UserController extends GetxController {
  final Rx<LoadState> state = Loading().obs;

  Future<void> loadUserInfo() async {
    state.value = Loading();
    try {
      await Future.delayed(const Duration(seconds: 1));
      state.value = Success('这是用户信息:张三,年龄 25');
    } catch (e) {
      state.value = Error('加载失败:$e');
    }
  }

  void reload() => loadUserInfo();
}

// UI
class UserPage extends StatelessWidget {
  final UserController controller = Get.put(UserController());

  @override
  Widget build(BuildContext context) {
    controller.loadUserInfo();
    
    return Scaffold(
      appBar: AppBar(title: const Text('用户信息')),
      body: Obx(() {
        return switch (controller.state.value) {
          Loading() => const Center(child: CircularProgressIndicator()),
          Success(:final data) => Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(Icons.check_circle, color: Colors.green, size: 64),
                  const SizedBox(height: 16),
                  Text('加载成功:$data'),
                ],
              ),
            ),
          Error(:final message) => Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Icon(Icons.error, color: Colors.red, size: 64),
                  const SizedBox(height: 16),
                  Text('出错了:$message'),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: controller.reload,
                    child: const Text('重试'),
                  ),
                ],
              ),
            ),
        };
      }),
    );
  }
}

三、主要区别

3.1 响应式 vs 一次性

特性 FutureBuilder sealed class + GetX
响应式 ❌ 不是响应式的,需要重建 widget ✅ 响应式的,状态变化自动更新 UI
重新加载 需要重建整个 widget 或使用 FutureBuilder 的 key ✅ 只需调用 controller 方法
状态持久化 ❌ 状态在 widget 重建时丢失 ✅ 状态在 Controller 中持久化

示例:

// FutureBuilder:重新加载需要重建 widget
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Future<String>? _future;
  
  @override
  void initState() {
    super.initState();
    _future = _loadData();
  }
  
  void _reload() {
    setState(() {
      _future = _loadData(); // 需要重建 widget
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: _future,
      builder: (context, snapshot) { ... },
    );
  }
}

// sealed class + GetX:重新加载只需调用方法
controller.reload(); // 状态自动更新,UI 自动刷新

3.2 状态复杂度

特性 FutureBuilder sealed class + GetX
状态数量 固定 4 种(none, waiting, active, done) ✅ 可以定义任意数量的状态
状态数据 只能通过 snapshot.datasnapshot.error ✅ 每个状态可以携带不同的数据
状态转换 简单的线性流程 ✅ 可以定义复杂的状态机

示例:

// FutureBuilder:只能处理简单的成功/失败
FutureBuilder<String>(
  future: _loadData(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) { ... }
    if (snapshot.hasError) { ... }
    if (snapshot.hasData) { ... }
  },
)

// sealed class:可以定义复杂的状态
sealed class ListState {}
class ListLoading extends ListState {}
class ListEmpty extends ListState {
  final String message;
  ListEmpty(this.message);
}
class ListSuccess extends ListState {
  final List<String> items;
  ListSuccess(this.items);
}
class ListPartialLoad extends ListState { // 部分加载
  final List<String> items;
  final bool hasMore;
  ListPartialLoad(this.items, this.hasMore);
}
class ListError extends ListState {
  final String message;
  final bool canRetry;
  ListError(this.message, this.canRetry);
}

3.3 类型安全

特性 FutureBuilder sealed class + GetX
类型安全 ⚠️ snapshot.data 可能是 null ✅ 编译器强制处理所有状态
空安全 需要手动检查 snapshot.hasData ✅ 状态和数据绑定,不会出现空值
完整性检查 ❌ 可能遗漏某些状态的处理 ✅ 编译器强制处理所有状态

示例:

// FutureBuilder:可能遗漏状态处理
FutureBuilder<String>(
  future: _loadData(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!); // 需要手动加 !,可能出错
    }
    // 如果忘记处理 error 状态,不会编译报错
    return const SizedBox.shrink();
  },
)

// sealed class:编译器强制处理所有状态
Obx(() {
  return switch (controller.state.value) {
    Loading() => ...,
    Success(:final data) => Text(data), // data 一定存在,类型安全
    Error(:final message) => ..., // 必须处理,否则编译报错
  };
})

3.4 适用场景

场景 FutureBuilder sealed class + GetX
一次性数据加载 ✅ 适合 ⚠️ 可以,但有点重
需要重新加载 ⚠️ 需要重建 widget ✅ 适合
复杂状态机 ❌ 不适合 ✅ 非常适合
状态持久化 ❌ 不适合 ✅ 适合
跨页面共享状态 ❌ 不适合 ✅ 适合
简单场景 ✅ 简单直接 ⚠️ 可能过度设计

四、如何选择?

4.1 使用 FutureBuilder 的场景

适合:

  • 一次性数据加载(如页面初始化时加载数据)
  • 简单的成功/失败场景
  • 不需要频繁重新加载
  • 状态不需要跨页面共享

示例:

// 页面初始化时加载用户信息,加载一次就够了
FutureBuilder<UserInfo>(
  future: UserService.getUserInfo(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const LoadingWidget();
    }
    if (snapshot.hasError) {
      return ErrorWidget(snapshot.error);
    }
    return UserInfoWidget(snapshot.data!);
  },
)

4.2 使用 sealed class + GetX 的场景

适合:

  • 需要频繁重新加载数据(如下拉刷新)
  • 复杂的状态机(如:加载中 → 部分加载 → 全部加载 → 刷新中)
  • 状态需要跨页面共享
  • 需要状态持久化
  • 需要类型安全和编译器检查

示例:

// 列表页面:需要下拉刷新、上拉加载、错误重试等
class ListController extends GetxController {
  final Rx<ListState> state = ListLoading().obs;

  Future<void> refresh() async {
    state.value = ListRefreshing();
    // ... 刷新逻辑
  }

  Future<void> loadMore() async {
    if (state.value is ListPartialLoad) {
      // ... 加载更多
    }
  }
}

五、混合使用

在实际项目中,可以混合使用两者:

class UserPage extends StatelessWidget {
  final UserController controller = Get.put(UserController());

  @override
  Widget build(BuildContext context) {
    // 使用 sealed class + GetX 管理可刷新的状态
    return Obx(() {
      return switch (controller.state.value) {
        Loading() => const LoadingWidget(),
        Success(:final data) => UserInfoWidget(data),
        Error(:final message) => ErrorWidget(message),
      };
    });
  }
}

// 但在某些一次性操作中,使用 FutureBuilder
class UserSettingsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Settings>(
      future: SettingsService.loadSettings(), // 只加载一次
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const LoadingWidget();
        }
        if (snapshot.hasError) {
          return ErrorWidget(snapshot.error);
        }
        return SettingsWidget(snapshot.data!);
      },
    );
  }
}

六、总结对比表

维度 FutureBuilder sealed class + GetX
响应式
类型安全 ⚠️ 一般 ✅ 强
状态复杂度 ⚠️ 简单 ✅ 灵活
重新加载 ⚠️ 需要重建 ✅ 简单
状态持久化
跨页面共享
代码量 ✅ 少 ⚠️ 多
学习成本 ✅ 低 ⚠️ 中
适用场景 一次性加载 复杂状态管理

七、结论

相似性:

  • 两者都遵循状态模式,根据状态渲染 UI
  • 都处理异步操作的状态(加载中、成功、失败)

区别:

  • FutureBuilder:简单、直接,适合一次性数据加载
  • sealed class + GetX:灵活、类型安全,适合复杂状态管理和响应式更新

建议:

  • 简单场景:用 FutureBuilder
  • 复杂场景:用 sealed class + GetX
  • 混合使用:根据具体需求选择最合适的方案
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容