04-Flutter状态管理终极指南-Riverpod3.x从入门到精通

9 阅读4分钟

🧠 Flutter 状态管理终极指南:Riverpod 3.x 从入门到精通

状态管理是 Flutter 开发的分水岭 — 入门用 setState,专业用 Riverpod。 Riverpod 3.x 带来了 Mutation、Ref.mounted、自动重试等重磅特性, 本文带你彻底掌握生产级状态管理。

写给谁:已有 Flutter 基础,想从 setState / Provider 升级到 Riverpod 的开发者。 读完你将掌握:Provider 类型选择、Notifier 模式、异步数据流、依赖注入、测试、以及 3.x 新特性。


📊 状态管理方案对比

方案复杂度学习曲线可测试性适合规模2026 状态
setState🟢 极低🔴 差单组件✅ 持续可用
Provider⭐⭐🟢 低🟡 中小型⚠️ 维护模式
Riverpod 3.x⭐⭐⭐🟡 中🟢 优秀全规模推荐
Bloc / Cubit⭐⭐⭐⭐🟠 较高🟢 优秀大型企业级✅ 稳定
GetX⭐⭐🟢 低🔴 差快速原型⚠️ 争议大

🔑 为什么选 Riverpod? 它是 Provider 作者的"重写版",解决了 Provider 的所有缺陷: 编译期安全、不依赖 BuildContext、完美的可测试性、灵活的依赖注入。


🏗 1. Riverpod 3.x 核心概念

三大角色

Provider(数据源)→ Ref(连接器)→ Widget(消费者)

📦 Provider:声明"数据从哪来"
🔗 Ref:读取和操作 Provider
🖥 Widget:监听 Provider 变化并重建 UI

环境搭建

# pubspec.yaml
dependencies:
  flutter_riverpod: ^3.2.1
  riverpod_annotation: ^4.0.2

dev_dependencies:
  riverpod_generator: ^4.0.3
  build_runner: ^2.4.13
  custom_lint:
  riverpod_lint:
// main.dart — 根组件包裹 ProviderScope
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

🧩 2. Provider 类型全解析

用代码生成(推荐方式)

Riverpod 3.x 推荐使用 @riverpod 注解 + 代码生成,编译器自动推断 Provider 类型。

import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'my_providers.g.dart';

// ① 简单值 Provider(只读,无状态)
@riverpod
String appTitle(Ref ref) => 'My App';

// ② 计算 / 派生 Provider(依赖其他 Provider)
@riverpod
String greeting(Ref ref) {
  final title = ref.watch(appTitleProvider);
  return 'Welcome to $title!';
}

// ③ 异步 Provider(API 请求)
@riverpod
Future<List<Product>> products(Ref ref) async {
  final dio = ref.watch(dioProvider);
  final response = await dio.get('/api/products');
  return (response.data as List).map((e) => Product.fromJson(e)).toList();
}

// ④ Stream Provider(实时数据)
@riverpod
Stream<int> countdown(Ref ref) {
  return Stream.periodic(const Duration(seconds: 1), (i) => 10 - i).take(11);
}

Notifier(有状态,可修改)

// ⑤ 同步 Notifier
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

// ⑥ 异步 Notifier(生产级常用)
@riverpod
class ProductList extends _$ProductList {
  @override
  Future<List<Product>> build() async {
    return _fetchProducts(page: 1);
  }

  Future<List<Product>> _fetchProducts({required int page}) async {
    final dio = ref.read(dioProvider);
    final response = await dio.get('/api/products', queryParameters: {'page': page});
    return (response.data['list'] as List).map((e) => Product.fromJson(e)).toList();
  }

  Future<void> refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => _fetchProducts(page: 1));
  }

  Future<void> loadMore(int page) async {
    final current = state.value ?? [];
    final more = await _fetchProducts(page: page);
    state = AsyncData([...current, ...more]);
  }
}

Provider 选型决策树

你需要什么样的状态?
│
├── 只读数据,无需修改?
│   ├── 同步数据 → @riverpod 函数(简单值)
│   ├── 异步数据(API)→ @riverpod Future 函数
│   └── 实时流数据 → @riverpod Stream 函数
│
└── 可修改状态?
    ├── 同步状态 → @riverpod class(Notifier)
    └── 异步状态 → @riverpod class + Future(AsyncNotifier)

⚡ 3. Riverpod 3.x 新特性深度解析

3.1 Mutation(副作用状态追踪)

3.x 最重磅特性!让 UI 能追踪"提交/删除/更新"等操作的 loading/success/error 状态。

@riverpod
class CartController extends _$CartController {
  @override
  List<CartItem> build() => [];

  // 标记为 @mutation,UI 可追踪此操作的状态
  @mutation
  Future<void> addItem(CartItem item) async {
    await ref.read(cartRepositoryProvider).addItem(item);
    state = [...state, item];
  }

  @mutation
  Future<void> removeItem(String itemId) async {
    await ref.read(cartRepositoryProvider).removeItem(itemId);
    state = state.where((e) => e.id != itemId).toList();
  }
}

// UI 中使用 — 每个 mutation 有独立的状态
class AddToCartButton extends ConsumerWidget {
  final CartItem item;
  const AddToCartButton({super.key, required this.item});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听 addItem 这个 mutation 的状态
    final addMutation = ref.watch(
      cartControllerProvider.addItem,
    );

    return ElevatedButton(
      onPressed: addMutation.isLoading
          ? null  // 加载中禁用按钮
          : () => ref.read(cartControllerProvider.notifier).addItem(item),
      child: addMutation.isLoading
          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
          : const Text('加入购物车'),
    );
  }
}
对比维度2.x 做法3.x Mutation
追踪按钮加载态手动维护 isLoading 变量自动追踪,mutation.isLoading
多个操作并行状态互相冲突每个 mutation 独立状态
错误处理手动 try/catch + 状态同步mutation.hasError 自动管理

3.2 Ref.mounted(安全检查)

@riverpod
class SearchController extends _$SearchController {
  @override
  List<SearchResult> build() => [];

  Future<void> search(String query) async {
    state = const AsyncLoading();
    final results = await ref.read(searchRepositoryProvider).search(query);

    // 3.x 新增:检查 Provider 是否还活着(防止内存泄漏)
    if (!ref.mounted) return;  // 如果已销毁,直接返回

    state = AsyncData(results);
  }
}

3.3 自动重试(Automatic Retry)

// 3.x 默认开启:异步 Provider 失败后自动重试
// 无需手动配置,框架自动处理瞬时错误(如网络抖动)

// 如果要自定义重试策略
@Riverpod(retry: myRetryLogic)
Future<UserProfile> userProfile(Ref ref) async {
  return ref.read(userRepositoryProvider).getProfile();
}

Duration? myRetryLogic(int retryCount, Object error) {
  // 最多重试 3 次,指数退避
  if (retryCount > 3) return null;  // 停止重试
  return Duration(seconds: math.pow(2, retryCount).toInt());
}

3.4 统一 Ref(告别泛型)

// 2.x(旧写法)— 需要泛型
// String myProvider(Ref<String> ref) => 'hello';

// 3.x(新写法)— 统一 Ref,无泛型
@riverpod
String myProvider(Ref ref) => 'hello';

🔗 4. 依赖注入与 Provider 组合

Provider 之间的依赖

// 基础设施层
@riverpod
Dio dio(Ref ref) {
  final token = ref.watch(authTokenProvider);
  return Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    headers: token != null ? {'Authorization': 'Bearer $token'} : null,
  ));
}

// 数据层 — 依赖 Dio
@riverpod
ProductRepository productRepository(Ref ref) {
  return ProductRepositoryImpl(dio: ref.watch(dioProvider));
}

// 业务层 — 依赖 Repository
@riverpod
Future<List<Product>> featuredProducts(Ref ref) async {
  final repo = ref.watch(productRepositoryProvider);
  return repo.getFeatured();
}

// 当 authToken 变化 → Dio 重建 → Repository 重建 → 数据自动刷新
// 🔥 这就是 Riverpod 的响应式依赖链!

依赖链可视化

authTokenProvider(登录状态变化)
    ↓ ref.watch
dioProvider(重建 Dio 实例,带新 Token)
    ↓ ref.watch
productRepositoryProvider(重建 Repository)
    ↓ ref.watch
featuredProductsProvider(自动重新请求数据)
    ↓ ref.watch
UI(自动重建展示新数据)

Family Provider(参数化)

// 3.x 新写法:参数通过构造函数传入
@riverpod
class ProductDetail extends _$ProductDetail {
  @override
  Future<Product> build({required String productId}) async {
    final repo = ref.watch(productRepositoryProvider);
    return repo.getById(productId);
  }
}

// 使用时
ref.watch(productDetailProvider(productId: 'prod-123'));

🖥 5. UI 集成模式

ConsumerWidget(推荐)

class ProductScreen extends ConsumerWidget {
  const ProductScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final productsAsync = ref.watch(productListProvider);

    return Scaffold(
      body: productsAsync.when(
        data: (products) => ProductListView(products: products),
        loading: () => const ShimmerLoading(),
        error: (e, st) => ErrorView(
          message: e.toString(),
          onRetry: () => ref.invalidate(productListProvider),
        ),
      ),
    );
  }
}

Consumer(局部监听,减少重建范围)

class ProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 这部分不需要状态,不会重建
        const HeaderWidget(),

        // 只有这部分监听状态,精确重建
        Consumer(
          builder: (context, ref, child) {
            final user = ref.watch(userProvider);
            return Text(user.value?.name ?? '加载中...');
          },
        ),

        // 这部分也不会重建
        const FooterWidget(),
      ],
    );
  }
}

ref.listen(副作用监听)

class LoginScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听状态变化执行副作用(不影响 UI 重建)
    ref.listen(loginControllerProvider, (prev, next) {
      if (next.hasError) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('登录失败: ${next.error}')),
        );
      }
      if (next.hasValue && next.value != null) {
        context.go('/home');  // 登录成功跳转
      }
    });

    final loginState = ref.watch(loginControllerProvider);

    return ElevatedButton(
      onPressed: loginState.isLoading ? null : () {
        ref.read(loginControllerProvider.notifier).login(
          email: emailController.text,
          password: passwordController.text,
        );
      },
      child: loginState.isLoading
          ? const CircularProgressIndicator()
          : const Text('登录'),
    );
  }
}

🧪 6. 测试:Riverpod 的杀手级优势

Provider 单元测试

// test/providers/counter_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  group('CounterProvider', () {
    test('初始值为 0', () {
      final container = ProviderContainer();
      addTearDown(container.dispose);

      expect(container.read(counterProvider), 0);
    });

    test('increment 增加 1', () {
      final container = ProviderContainer();
      addTearDown(container.dispose);

      container.read(counterProvider.notifier).increment();
      expect(container.read(counterProvider), 1);
    });

    test('多次操作', () {
      final container = ProviderContainer();
      addTearDown(container.dispose);

      final notifier = container.read(counterProvider.notifier);
      notifier.increment();
      notifier.increment();
      notifier.decrement();
      expect(container.read(counterProvider), 1);
    });
  });
}

Mock 依赖(Override)

// 测试时替换真实 API 为 Mock
test('获取商品列表', () async {
  final mockRepo = MockProductRepository();
  when(mockRepo.getAll()).thenAnswer((_) async => [
    Product(id: '1', name: '测试商品', price: 99.0),
  ]);

  final container = ProviderContainer(
    overrides: [
      // 🔥 核心能力:用 Mock 替换真实依赖
      productRepositoryProvider.overrideWithValue(mockRepo),
    ],
  );
  addTearDown(container.dispose);

  // 等待异步 Provider 完成
  await container.read(productListProvider.future);
  final products = container.read(productListProvider).value!;

  expect(products.length, 1);
  expect(products[0].name, '测试商品');
});

Widget 测试

testWidgets('商品列表展示正确', (tester) async {
  await tester.pumpWidget(
    ProviderScope(
      overrides: [
        productListProvider.overrideWith((ref) async {
          return [
            Product(id: '1', name: 'Flutter 实战', price: 59.0),
            Product(id: '2', name: 'Dart 入门', price: 39.0),
          ];
        }),
      ],
      child: const MaterialApp(home: ProductScreen()),
    ),
  );

  await tester.pumpAndSettle();

  expect(find.text('Flutter 实战'), findsOneWidget);
  expect(find.text('Dart 入门'), findsOneWidget);
});

📋 7. 生产级最佳实践

项目结构

lib/
├── core/
│   └── providers/
│       ├── dio_provider.dart       # 网络层
│       └── storage_provider.dart   # 缓存层
├── features/
│   └── product/
│       ├── data/
│       │   └── product_repository.dart
│       ├── domain/
│       │   └── product.dart
│       └── presentation/
│           ├── controllers/
│           │   └── product_controller.dart  # @riverpod Notifier
│           ├── screens/
│           │   └── product_screen.dart      # ConsumerWidget
│           └── widgets/
│               └── product_card.dart        # StatelessWidget

常见错误速查

错误后果修复
build() 中用 ref.read状态变化 UI 不更新读数据用 ref.watch,写操作用 ref.read
callback 中用 ref.watch不必要的监听和重建callback 中用 ref.read
忘记 ref.mounted 检查异步完成后 Provider 已销毁异步操作后加 if (!ref.mounted) return
ProviderScope 嵌套混乱状态隔离不符合预期全局只用一个根 ProviderScope
不用代码生成手写 Provider易出错,写法冗长统一用 @riverpod + build_runner

✅ Riverpod 掌握 Checklist

基础掌握

  • 理解 Provider / Notifier / Ref 三大角色
  • 能用 @riverpod 创建各种类型 Provider
  • 掌握 ref.watch vs ref.read vs ref.listen 的区别
  • 理解 AsyncValue.when() 三态处理
  • 能运行 build_runner 生成代码

进阶掌握

  • 掌握 Provider 依赖链和响应式刷新
  • 使用 Family Provider 传参
  • 使用 Mutation 追踪副作用状态
  • 使用 ref.invalidate() 手动刷新
  • 理解 keepAlive 和自动销毁机制

生产级

  • 能用 ProviderContainer + Override 写单元测试
  • 能用 ProviderScope overrides 写 Widget 测试
  • 配置 riverpod_lint 静态检查
  • 使用 Riverpod DevTools 调试

Provider 是"推"模型 — 你告诉 Widget 数据在哪,Widget 被动接收。 Riverpod 是"拉"模型 — Widget 主动声明"我需要什么",框架负责送达和更新。 从"推"到"拉"的思维转变,就是从初级到高级 Flutter 开发者的跨越。