Flutter 列表真正的威力,不在 ListView,而在:
👉 列表 = 状态的投影结果
一、为什么 Flutter 列表必须配状态管理?
新手写法:
Dart
setState(() {
list.addAll(newData);
});
问题:
- 业务逻辑混在 UI
- 无法复用
- 无法测试
- 分页、错误、加载状态混乱
企业级目标:
👉 UI 只关心"状态"
👉 所有列表行为 = 状态机
二、列表的标准状态建模(架构核心)
一个工程级列表,最少包含:
Dart
class ListState<T> {
final List<T> items;
final bool isLoading;
final bool isRefreshing;
final bool hasMore;
final String? error;
const ListState({
this.items = const [],
this.isLoading = false,
this.isRefreshing = false,
this.hasMore = true,
this.error,
});
}
👉 这是 Flutter 列表的"领域模型(Domain State)"。
三、Riverpod Controller 层(替代 ViewModel)
Dart
class UserListController extends StateNotifier<ListState<User>> {
UserListController() : super(const ListState());
Future<void> refresh() async {
state = state.copyWith(isRefreshing: true);
final data = await repo.fetch(page: 1);
state = state.copyWith(
items: data,
isRefreshing: false,
hasMore: true,
);
}
Future<void> loadMore() async {
if (!state.hasMore || state.isLoading) return;
state = state.copyWith(isLoading: true);
final data = await repo.fetchNext();
state = state.copyWith(
items: [...state.items, ...data],
isLoading: false,
hasMore: data.isNotEmpty,
);
}
}
Dart
final userListProvider =
StateNotifierProvider<UserListController, ListState<User>>(
(ref) => UserListController(),
);
👉 这里就是 MVVM / MVI 的 ViewModel / Reducer 层。
四、UI 层(纯渲染,不写业务)
Dart
class UserListPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(userListProvider);
final controller = ref.read(userListProvider.notifier);
return RefreshIndicator(
onRefresh: controller.refresh,
child: ListView.builder(
itemCount: state.items.length + 1,
itemBuilder: (context, index) {
if (index == state.items.length) {
return _Footer(state);
}
return UserItem(state.items[index]);
},
),
);
}
}
Dart
class _Footer extends StatelessWidget {
final ListState state;
const _Footer(this.state);
@override
Widget build(BuildContext context) {
if (state.isLoading) {
return Center(child: CircularProgressIndicator());
}
if (!state.hasMore) {
return Center(child: Text("没有更多了"));
}
return SizedBox.shrink();
}
}
✔ UI = 纯函数
✔ 可测试
✔ 可替换
✔ 可维护
五、滚动触底触发分页
Dart
final scrollController = ScrollController();
scrollController.addListener(() {
if (scrollController.position.pixels >
scrollController.position.maxScrollExtent - 200) {
ref.read(userListProvider.notifier).loadMore();
}
});
👉 滚动是输入
👉 Controller 决定状态变化
👉 UI 只是结果
六、这套架构解决了什么?
| 问题 | 解决 |
|---|---|
| setState 混乱 | 状态集中管理 |
| 分页难写 | 行为模型化 |
| 异常难处理 | error 状态 |
| UI 不可测 | Controller 可单测 |
| 列表难复用 | Domain State 可复用 |
👉 这是"架构级列表"。
七、架构总结
Flutter 列表的终极形式:
Dart
Input(滚动/点击)
↓
Controller(Riverpod)
↓
State(列表领域模型)
↓
ListView(UI渲染)