Riverpod视图状态管理方案对比:优劣分析与优化建议
Riverpod中列表页面状态管理的三种模式分析与优化建议
本文针对Riverpod中管理视图状态(包含列表数据、排序规则、分页信息、搜索筛选条件等)的三种候选模式,逐一分析优缺点并给出优化建议。
模式A:为@freezed State类使用AsyncNotifier
核心逻辑
将所有页面状态(列表数据、排序规则、筛选条件)封装在一个@freezed的UsersListPageState中,通过AsyncNotifier管理整个状态的异步生命周期,额外拆分出usersProvider和orderByProvider供UI单独监听。
优缺点分析
优点
- 状态高度内聚,所有页面相关状态统一在一个State类中,逻辑边界清晰
- 借助AsyncNotifier的异步状态封装,天然支持loading/error/data三种状态的全局管理
缺点
- 整个State被包裹在
AsyncValue中,UI无法直接监听非异步字段(如orderBy),必须额外创建衍生Provider,增加代码冗余 - 修改非异步字段时需要先获取
state.valueOrNull,存在繁琐的空值判断逻辑 - 刷新数据时需要重新构建整个State对象,状态更新粒度不够精细
优化建议
- 移除冗余衍生Provider:UI层直接通过
select方法监听状态中的具体字段,无需额外创建usersProvider和orderByProvider:
final users = ref.watch(usersListPageNotifierProvider.select((state) => state.value?.users ?? [])); final orderBy = ref.watch(usersListPageNotifierProvider.select((state) => state.value?.orderBy ?? "id"));
- 简化状态更新逻辑:利用
whenData方法简化非异步字段的更新,避免空值判断:
void setOrderBy(String orderBy) { state = state.whenData((data) => data.copyWith(orderBy: orderBy)); refresh(); }
- 拆分异步与同步状态:将异步列表数据和同步筛选/排序状态分离,减少AsyncValue对同步字段的不必要包裹
代码示例
// list1.dart import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:openapi/openapi.dart'; import 'package:dio/dio.dart'; import 'package:flutter_app/general_provider.dart'; part 'list1.g.dart'; part 'list1.freezed.dart'; @freezed class UsersListPageState with _$UsersListPageState { const factory UsersListPageState({ @Default([]) List<ModelUser> users, @Default("id") String orderBy, @Default("") String filter, }) = _UsersListPageState; } @riverpod class UsersListPageNotifier extends _$UsersListPageNotifier { Future<List<ModelUser>> _fetchUsers() async { final cancelToken = CancelToken(); ref.onDispose(() => cancelToken.cancel()); final String orderBy = state.value?.orderBy ?? "id"; final users = await ref .read(openApiProvider) .getUserApi() .searchUser(orderBy: orderBy, cancelToken: cancelToken) .then((res) => res.data!.users!.toList()); return users; } @override Future<UsersListPageState> build() async { return const UsersListPageState().copyWith(users: await _fetchUsers()); } setOrderBy(String orderBy) { final previousState = state.valueOrNull; if (previousState == null) { return; } state = AsyncValue.data(previousState.copyWith(orderBy: orderBy)); refresh(); } setFilter(String filter) { final previousState = state.valueOrNull; if (previousState == null) { return; } state = AsyncValue.data(previousState.copyWith(filter: filter)); refresh(); } refresh() async { final previousState = state.valueOrNull; if (previousState == null) { return; } state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { final users = await _fetchUsers(); return previousState.copyWith(users: users); }); } } @riverpod Future<List<ModelUser>> users(UsersRef ref) { return ref.watch( usersListPageNotifierProvider.selectAsync((data) => data.users) ); } @riverpod String orderBy(OrderByRef ref) { return ref.watch( usersListPageNotifierProvider.select((data) => data.value?.orderBy ?? "") ); }
模式B:为@freezed State类使用Notifier
核心逻辑
将异步列表数据单独封装在State的AsyncValue字段中,同步的排序、筛选字段直接作为普通字段,通过Notifier管理整个State对象,初始化逻辑通过ref.listenSelf实现。
优缺点分析
优点
- 状态拆分更合理:异步数据和同步状态分离,UI可直接监听同步字段,无需额外衍生Provider
- 状态更新粒度精细:修改同步字段或异步数据时,仅更新对应部分,避免全局状态重构
- 代码结构清晰,AsyncValue仅包裹需要异步处理的列表数据
缺点
- 初始化逻辑需通过
ref.listenSelf实现,相比AsyncNotifier的build方法直接返回异步结果,写法不够直观 - 手动处理异步数据的loading/error状态,需在
refresh方法中手动切换状态,增加代码复杂度
优化建议
- 简化初始化逻辑:将初始化的异步请求放在
build方法中通过async/await实现,利用Notifier的状态更新机制完成初始化:
@override Future<UsersListPageState> build() async { final users = await AsyncValue.guard(_fetchUsers); return UsersListPageState(users: users); }
- 封装异步更新逻辑:提取通用的异步更新方法,避免重复的loading/error处理:
Future<void> _updateUsers() async { state = state.copyWith(users: const AsyncValue.loading()); state = state.copyWith(users: await AsyncValue.guard(_fetchUsers)); }
随后在refresh、setOrderBy、setFilter中直接调用_updateUsers()即可
3. 增加防抖处理:针对筛选、排序等频繁触发的操作,增加防抖逻辑,避免频繁发起网络请求
代码示例
// list2.dart import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:openapi/openapi.dart'; import 'package:dio/dio.dart'; import 'package:flutter_app/general_provider.dart'; part 'list2.g.dart'; part 'list2.freezed.dart'; @freezed class UsersListPageState with _$UsersListPageState { const factory UsersListPageState({ @Default(AsyncValue.loading()) AsyncValue<List<ModelUser>> users, @Default("id") String orderBy, @Default("") String filter, }) = _UsersListPageState; } @riverpod class UsersListPageNotifier extends _$UsersListPageNotifier { Future<List<ModelUser>> _fetchUsers() async { final cancelToken = CancelToken(); ref.onDispose(() => cancelToken.cancel()); final String orderBy = state.orderBy; final users = await ref .read(openApiProvider) .getUserApi() .searchUser(orderBy: orderBy, cancelToken: cancelToken) .then((res) => res.data!.users!.toList()); return users; } @override UsersListPageState build() { ref.listenSelf((previous, next) { if (previous != null) { return; } _fetchUsers().then((List<ModelUser> users) { state = state.copyWith(users: AsyncValue.data(users)); }); }); return const UsersListPageState(); } setOrderBy(String orderBy) { state = state.copyWith(orderBy: orderBy); refresh(); } setFilter(String filter) { state = state.copyWith(filter: filter); refresh(); } refresh() async { state = state.copyWith(users: const AsyncValue.loading()); final users = await AsyncValue.guard(() async { return _fetchUsers(); }); state = state.copyWith(users: users); } }
模式C:不创建State类,直接使用AsyncNotifier和Notifier
核心逻辑
将列表数据、排序规则、筛选条件拆分为独立的Provider:usersProvider(AsyncNotifier)、orderByProvider(Notifier)、filterProvider(Notifier),通过ref.invalidate实现状态变更后的列表刷新。
优缺点分析
优点
- 代码量最少,实现最简洁,每个Provider只负责单一职责
- 无需维护复杂的State类,逻辑直观,上手成本低
缺点
- 状态分散,多个Provider之间的依赖关系需手动维护,逻辑连贯性差,后期难以理解和维护
- 状态变更的触发逻辑分散在各个Notifier中,容易出现遗漏或错误
- 无法统一管理页面整体状态,例如无法一次性重置所有页面状态
优化建议
- 增加组合Provider:创建页面级组合Provider,聚合所有相关Provider,方便UI层统一监听:
@riverpod class UsersListPageState extends _$UsersListPageState { @override ({AsyncValue<List<ModelUser>> users, String orderBy, String filter}) build() { final users = ref.watch(usersProvider); final orderBy = ref.watch(orderByProvider); final filter = ref.watch(filterProvider); return (users: users, orderBy: orderBy, filter: filter); } }
- 封装状态重置逻辑:创建专门方法统一重置所有页面状态:
@riverpod void resetUsersListPage(ResetUsersListPageRef ref) { ref.read(orderByProvider.notifier).state = "id"; ref.read(filterProvider.notifier).state = ""; ref.invalidate(usersProvider); }
- 统一状态变更触发逻辑:将排序、筛选变更后的列表刷新逻辑封装到通用方法中,避免重复调用
ref.invalidate
代码示例
// list3.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:openapi/openapi.dart'; import 'package:dio/dio.dart'; import 'package:flutter_app/general_provider.dart'; part 'list3.g.dart'; @riverpod class Users extends _$Users { Future<List<ModelUser>> _fetchUsers() async { final cancelToken = CancelToken(); ref.onDispose(() => cancelToken.cancel()); final String orderBy = ref.watch(orderByProvider); final users = await ref .read(openApiProvider) .getUserApi() .searchUser(orderBy: orderBy, cancelToken: cancelToken) .then((res) => res.data!.users!.toList()); return users; } @override Future<List<ModelUser>> build() async { return await _fetchUsers(); } } @riverpod class OrderBy extends _$OrderBy { @override String build() => "id"; void set(String orderBy) { state = orderBy; ref.invalidate(usersProvider); } } @riverpod class Filter extends _$Filter { @override String build() => ""; void set(String filter) { state = filter; ref.invalidate(usersProvider); } }
视图代码示例
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:openapi/openapi.dart'; import 'package:flutter_app/providers/users/list1.dart'; // import 'package:flutter_app/providers/users/list2.dart'; // import 'package:flutter_app/providers/users/list3.dart'; class UsersListPage extends HookConsumerWidget { const UsersListPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // list1, list3 final AsyncValue<List<ModelUser>> users = ref.watch(usersProvider); final String orderBy = ref.watch(orderByProvider); // list2 // final AsyncValue<List<ModelUser>> users = ref.watch(usersListPageNotifierProvider.select((value) => value.users)); // final String orderBy = ref.watch(usersListPageNotifierProvider.select((value) => value.orderBy)); return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("TEST"), ), body: users.when( data: (users) { print('rendering!!!'); return ListView( children: users.map((user) { return ListTile( leading: const Icon(Icons.map), title: Text('${user.lastName ?? ""} ${user.firstName ?? ""}'), onTap: () { context.go('/users/${user.id}'); }, ); }).toList(), ); }, error: (err, stack) { print('error!!!'); return Text('Error: $err'); }, loading: () { print('loading!!!'); return const CircularProgressIndicator(); }, ), floatingActionButton: FloatingActionButton( onPressed: () { // list1, list2 ref.read(usersListPageNotifierProvider.notifier).setOrderBy("id desc"); // list3 // ref.read(orderByProvider.notifier).set("id desc"); }, tooltip: 'Increment', child: const Icon(Icons.sort), ), ); } }
总结
- 模式A:适合需要全局统一管理页面状态、重视逻辑内聚的场景,优化后可减少冗余代码
- 模式B:兼顾状态内聚和更新粒度的平衡,是最推荐的方案,优化后初始化逻辑更直观,代码更简洁
- 模式C:适合小型项目或简单页面,快速实现但后期维护成本高,优化后可提升状态的连贯性
内容的提问来源于stack exchange,提问作者Youya
相关产品推荐
相关产品推荐

