状态管理选型:Provider 与 Riverpod

状态管理是 Flutter 社区争论最多的话题,但有一条底线:不要为了用而用。只有局部状态的页面,setState 就是最优解;当状态需要跨页面共享、需要缓存服务端数据、或需要独立可测试时,才值得引入状态管理库。本章先给判断标准,再给 providerriverpod 的完整示例,最后给选型对照表。

什么时候真的需要状态管理

信号是否必须更轻的替代
状态只在单个页面内不需要StatefulWidget + setState
兄弟组件共享一层状态不需要状态提升到父级
三层以上跨层传递需要先试 InheritedWidget,再上库
多页面共享登录态、购物车需要Provider / Riverpod
服务端数据的加载与缓存需要Riverpod 的异步 Provider 更顺

InheritedWidget 一句话原理:它是内置的"沿树向下广播数据"机制,子级订阅后只有订阅者重建;ThemeMediaQuery 都是它的实现,provider 与 Riverpod 都是在这个机制上加了一层好用的 API。

provider:ChangeNotifier + ChangeNotifierProvider + Consumer

flutter pub add provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 状态模型:继承 ChangeNotifier,改完数据调用 notifyListeners
class CartModel extends ChangeNotifier {
  final List<String> _items = [];
  List<String> get items => List.unmodifiable(_items); // 对外只读
  void add(String name) {
    _items.add(name);
    notifyListeners(); // 通知所有监听者刷新
  }
}

void main() => runApp(
      // 在根部 Provide,全树可见
      ChangeNotifierProvider(create: (_) => CartModel(), child: const MyApp()),
    );

class CartPage extends StatelessWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.watch<CartModel>().items.length; // 订阅:变化即重建
    return Scaffold(
      appBar: AppBar(title: Text('购物车($count)')),
      body: Consumer<CartModel>( // Consumer 把重建范围收窄到 builder 内部
        builder: (context, cart, _) => ListView(children: [for (final n in cart.items) ListTile(title: Text(n))]),
      ),
      floatingActionButton: FloatingActionButton(
        // 回调里只取实例、不订阅,用 read(等价于 listen: false)
        onPressed: () => context.read<CartModel>().add('商品 ${DateTime.now().millisecond}'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

riverpod:ProviderScope + ref.watch + AsyncValue

flutter pub add flutter_riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 同步状态:Notifier 的 build 返回初始值,state 读写由框架托管
final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);

class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0;
  void add() => state = state + 1;
}

// 异步状态:FutureProvider 自动托管 loading / error / data
final newsProvider = FutureProvider<List<String>>((ref) async {
  await Future<void>.delayed(const Duration(seconds: 1)); // 换成 dio 请求
  return const ['Flutter 3.x 发布', 'Impeller 默认启用'];
});

void main() => runApp(const ProviderScope(child: MyApp())); // 必须包一层 ProviderScope

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider); // watch:订阅并随变化重建
    final news = ref.watch(newsProvider);
    return Scaffold(
      appBar: AppBar(title: Text('计数 $count')),
      body: news.when(
        // AsyncValue.when 强制覆盖三种状态,漏写编译不过
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (e, st) => Center(child: Text('加载失败:$e')),
        data: (list) => ListView(children: [for (final s in list) ListTile(title: Text(s))]),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).add(), // read:回调里只取一次
        child: const Icon(Icons.add),
      ),
    );
  }
}

ref.watch 会订阅、值变化时重建;ref.read 不订阅,只能用在点击回调、initState 这类"只要一次"的地方。Riverpod 还有 AsyncNotifierProvider(异步状态 + 写方法)与 StreamProvider,都能沿用上面的写法。

选型对照表

维度setStateproviderriverpod
学习成本最低
样板代码量最少中(需声明 Provider)
跨页面共享很麻烦方便方便
编译期安全——弱,ProviderNotFoundException 运行期才报
异步数据三态自己写自己写AsyncValue 内置
适合项目规模单页小应用中小型、存量 Provider 生态中大型新项目

取舍建议:单人小工具只用 setState;中型业务(10~30 个页面)用 provider 足够,尤其团队已熟悉 ChangeNotifier;新项目且服务端数据多,直接上 riverpodAsyncValue 能省下大量手写状态样板;已用 BLoC 的团队不必为追新而迁移。

反例:小项目上重型方案

一个只有计数器的小页面,用 Riverpod 包了 ProviderScope → Provider → ConsumerWidget → ref.watch → Notifier 五层,实际等价于一句 setState(() => _count++)

判断公式很朴素:状态管理库的成本 ≈ 新增概念数 × 新增文件数 ÷ 参与人数。另一个常见反例是把所有东西都塞进全局 Provider,任何一处改动都触发大片重建——状态该近就近,跨层才共享。

flutter analyze     # 检查 Provider 泛型写错、依赖未声明
flutter test

小结:先用 setState,跨层共享再上状态管理;providerChangeNotifier + ChangeNotifierProvider + Consumer/watchriverpodProviderScope + NotifierProvider/FutureProvider + ref.watch/ref.read + AsyncValue.whenbuildwatch、回调里 read;选型看项目规模与团队习惯,不为用而用。

笔记加载中…