You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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对象,状态更新粒度不够精细

优化建议

  1. 移除冗余衍生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"));
  1. 简化状态更新逻辑:利用whenData方法简化非异步字段的更新,避免空值判断:
void setOrderBy(String orderBy) {
  state = state.whenData((data) => data.copyWith(orderBy: orderBy));
  refresh();
}
  1. 拆分异步与同步状态:将异步列表数据和同步筛选/排序状态分离,减少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方法中手动切换状态,增加代码复杂度

优化建议

  1. 简化初始化逻辑:将初始化的异步请求放在build方法中通过async/await实现,利用Notifier的状态更新机制完成初始化:
@override
Future<UsersListPageState> build() async {
  final users = await AsyncValue.guard(_fetchUsers);
  return UsersListPageState(users: users);
}
  1. 封装异步更新逻辑:提取通用的异步更新方法,避免重复的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中,容易出现遗漏或错误
  • 无法统一管理页面整体状态,例如无法一次性重置所有页面状态

优化建议

  1. 增加组合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);
  }
}
  1. 封装状态重置逻辑:创建专门方法统一重置所有页面状态:
@riverpod
void resetUsersListPage(ResetUsersListPageRef ref) {
  ref.read(orderByProvider.notifier).state = "id";
  ref.read(filterProvider.notifier).state = "";
  ref.invalidate(usersProvider);
}
  1. 统一状态变更触发逻辑:将排序、筛选变更后的列表刷新逻辑封装到通用方法中,避免重复调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 05:42:04