Flutter状态管理选型:Provider、Riverpod还是Bloc

Flutter状态管理选型:Provider、Riverpod还是Bloc

Flutter 状态管理是一个老生常谈但又绕不开的话题。团队每次新开项目,选型阶段总是吵得不可开交:有人觉得 Provider 够用,有人坚持要上 Riverpod,还有人认为只有 BLoC 才能应对复杂场景。

我经历过多次这种讨论,也踩过不少坑。这篇想聊聊这三个方案的实际适用场景,以及怎么根据团队和项目情况做选择。

先说结论

如果你在选型阶段纠结,可以先看这个判断逻辑:

  • 小团队、快速迭代、团队成员水平不一:选 Provider,上手成本最低
  • 中型项目、需要一定扩展性、想减少样板代码:选 Riverpod
  • 大型复杂项目、团队有函数式编程背景、需要严格分层:选 BLoC

但结论永远只是参考,具体还是要看场景。

Provider:够用,但有边界

Provider 是 Flutter 官方推荐的方案,生态成熟,文档丰富。接入成本低,一个 ChangeNotifierProvider 就能把状态管起来:

class CartModel extends ChangeNotifier {
  final List<Product> _items = [];

  List<Product> get items => _items;

  void add(Product product) {
    _items.add(product);
    notifyListeners();
  }
}
ChangeNotifierProvider(
  create: (_) => CartModel(),
  child: CartPage(),
)

这套模式对于电商购物车、简单设置页、单个业务模块完全够用。问题是随着页面变多、状态关联变复杂,Provider 的问题就开始暴露:

问题 1:依赖地狱

页面 A 需要同时获取用户信息、购物车、搜索历史,嵌套写出来大概是这样:

MultiProvider(
  providers: [
    Provider(create: (_) => UserService()),
    Provider(create: (_) => ProductService()),
    ChangeNotifierProxyProvider<UserService, CartModel>(
      create: (_) => CartModel(),
      update: (_, user, cart) => cart!..setUser(user),
    ),
    ChangeNotifierProxyProvider2<CartModel, ProductService, SearchHistory>(
      create: (_) => SearchHistory(),
      update: (_, cart, products, history) => history!..update(cart, products),
    ),
  ],
)

ProxyProvider 的嵌套层数一多,代码就很难维护,而且类型推断经常出问题。

问题 2:重建粒度粗

Provider 的消费者默认是整 Widget 重建:

Consumer<CartModel>(
  builder: (_, cart, __) {
    // cart.items 变化时,整个 build 方法都会执行
    return Column(
      children: [
        Text('共 ${cart.items.length} 件商品'),
        // 其他 Widget...
      ],
    );
  },
)

如果你只想监听 cart.totalPrice,但 cart 对象任何变化都会触发重建。这在复杂页面里是性能隐患。

问题 3:dispose 容易漏

ChangeNotifierdispose 需要手动调用,大型应用里很容易出现状态没被正确清理的情况,尤其是页面跳转逻辑复杂时。

Riverpod:Provider 的升级版

Riverpod 可以理解为 Provider 的完全替代方案,核心改进是:编译时安全、依赖注入更清晰、重建粒度更精细。

核心概念:Provider

Riverpod 的 Provider 体系比 Provider 丰富很多:

// 最基础的 provider
final userNameProvider = Provider<String>((ref) => 'Guest');

// 带状态的 provider
final cartProvider = StateNotifierProvider<CartNotifier, CartState>((ref) {
  return CartNotifier();
});

// 异步数据 provider
final productsProvider = FutureProvider<List<Product>>((ref) async {
  return ProductService().fetchProducts();
});

依赖另一个 Provider

final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
  return UserNotifier(ref.read(authProvider));
});

final cartTotalProvider = Provider<double>((ref) {
  final cart = ref.watch(cartProvider);
  return cart.items.fold(0, (sum, item) => sum + item.price);
});

这里 ref.watchref.read 的区别很重要:watch 用于响应式依赖,当被依赖的 Provider 变化时,当前 Provider 会自动重建;read 是只读访问,不会建立响应式依赖,常用于事件处理中。

自动 dispose

Riverpod 的 Provider 有完整的生命周期管理,不存在 ChangeNotifier 漏 dispose 的问题:

final repositoryProvider = Provider<ArticleRepository>((ref) {
  final repository = ArticleRepository();
  ref.onDispose(() => repository.dispose());
  return repository;
});

实战建议

Riverpod 在这些场景下优势明显:

  1. 跨页面共享状态:比如登录状态、用户权限信息,Provider 需要 MultiProvider 嵌套,Riverpod 直接在任意地方 ref.watch

  2. 复杂计算派生状态:购物车总价、筛选后的列表,Riverpod 的 Provider 组合比 ChangeNotifier 更轻量

  3. 异步数据流:网络请求状态(loading、error、data),用 AsyncValue 处理比手动管理状态干净太多

final articleProvider = FutureProvider.family<Article, String>((ref, id) async {
  return ArticleRepository().fetchArticle(id);
});

// 消费端
articleProvider.when(
  data: (article) => ArticleView(article: article),
  loading: () => CircularProgressIndicator(),
  error: (e, _) => ErrorView(error: e),
)

BLoC:严格分层的代价与收益

BLoC 是这三个方案里最重的,但换来的是最清晰的分层和最严格的单向数据流。

核心模式

BLoC 把 UI 和业务逻辑完全隔开,靠 Events 输入、States 输出:

// Events
abstract class CartEvent {}
class AddItem extends CartEvent { final Product product; }
class RemoveItem extends CartEvent { final String productId; }
class Checkout extends CartEvent {}

// State
class CartState {
  final List<CartItem> items;
  final bool isCheckingOut;
  final String? error;
}

// BLoC
class CartBloc extends Bloc<CartEvent, CartState> {
  CartBloc() : super(CartState(items: [])) {
    on<AddItem>((event, emit) {
      emit(state.copyWith(items: [...state.items, event.product]));
    });

    on<RemoveItem>((event, emit) {
      emit(state.copyWith(
        items: state.items.where((i) => i.id != event.productId).toList(),
      ));
    });

    on<Checkout>((event, emit) async {
      emit(state.copyWith(isCheckingOut: true));
      try {
        await CheckoutService().submit(state.items);
        emit(state.copyWith(isCheckingOut: false, items: []));
      } catch (e) {
        emit(state.copyWith(isCheckingOut: false, error: e.toString()));
      }
    });
  }
}

UI 层干净

BlocBuilder<CartBloc, CartState>(
  builder: (context, state) {
    if (state.isCheckingOut) {
      return LoadingView();
    }
    return CartView(
      items: state.items,
      onAdd: (p) => context.read<CartBloc>().add(AddItem(p)),
      onRemove: (id) => context.read<CartBloc>().add(RemoveItem(id)),
      onCheckout: () => context.read<CartBloc>().add(Checkout()),
    );
  },
)

BLoC 的真实成本

BLoC 不是银弹,它有明显的接入成本:

  1. 学习曲线:团队必须理解 Event-State 模式,理解 emit 是不可变的,理解为什么不能同步调用 addread 状态
  2. 样板代码多:每个业务模块都要写 Event、State、BLoC 三个类,即使是很简单的逻辑
  3. 依赖注入复杂:BLoC 通常需要通过 BlocProvider 注入,跨模块依赖时 provider 嵌套不比 Provider 好多少

什么时候值得用 BLoC

BLoC 的价值在于强制约束。当你的团队满足以下条件时,BLoC 的约束反而是保护:

  • 项目成员超过 5 人,代码一致性要求高
  • 业务逻辑复杂,有大量异步流程和状态转换
  • 需要做详细的单元测试,BLoC 的纯函数式设计天然适合测试
  • 产品后期维护周期长,需要严格的代码规范

对于简单活动页、内部工具类项目,BLoC 的重量会拖累开发速度。

实际项目中的混用策略

这三个方案不是非此即彼的关系。我目前项目里的做法是:

  • 全局状态用 Riverpod:用户登录态、主题设置、全局配置
  • 业务模块用 BLoC:订单流程、支付流程、复杂表单
  • 局部 UI 状态用 setState:弹窗显隐、展开收起、微交互
// 全局配置 - Riverpod
final settingsProvider = StateNotifierProvider<SettingsNotifier, Settings>((ref) {
  return SettingsNotifier();
});

// 订单模块 - BLoC
class OrderBloc extends Bloc<OrderEvent, OrderState> {
  // 订单相关的复杂逻辑
}

// 页面内局部状态 - setState
class ProductDetailPage extends StatefulWidget {
  
  State<ProductDetailPage> createState() => _ProductDetailPageState();
}

class _ProductDetailPageState extends State<ProductDetailPage> {
  bool _isExpanded = false;
  // 局部 UI 状态,不需要上升全局
}

这种分层的好处是:全局状态集中管理,复杂业务模块独立测试,局部状态不引入额外复杂度。

选型建议的量化参考

如果还是纠结,可以看这几个指标:

指标ProviderRiverpodBLoC
上手难度
样板代码量
编译时安全部分有
测试难度
团队门槛
适合项目规模

还有一个实操判断:如果你的页面 build() 方法里出现了超过 3 层的 Consumercontext.watch,就该考虑迁移到 Riverpod 或者 BLoC 了。

最后

状态管理选型没有标准答案,但有一个避坑原则:不要为了架构而架构

很多团队上来就决定用 BLoC,结果半年后项目黄了,代码倒是写得挺 BLoC。技术选型要跟着业务节奏走,小步快跑的项目用 Provider 快速落地,等业务稳定了再根据需要引入更重的方案。

最怕的是一开始选了最轻量的方案,结果业务复杂了硬撑,导致状态逻辑一团乱麻。这种情况不如早点用 Riverpod 重构,至少类型安全能帮你少踩很多坑。

最后更新 4/20/2026, 4:48:48 AM