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.data 和 snapshot.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 - 混合使用:根据具体需求选择最合适的方案