开篇:为什么状态管理是 Flutter 的核心难题?
Flutter 的声明式 UI 本质上是"状态驱动视图"(State → Widget)。当应用规模较小时,StatefulWidget 的 setState 足以应付;但随着页面增多、数据流转复杂化,状态如何组织、如何跨页面共享、如何避免不必要的重建,成为所有 Flutter 开发者必须面对的架构问题。
Flutter 社区的状态管理方案多达十几种,从官方推荐的 Provider 到社区热门的 Riverpod、BLoC,从函数式的 Redux 到响应式的 MobX,每一种方案都有其适用的场景和团队偏好。本章节将深入分析各方案的核心原理、使用方式和选型建议,帮助你为项目找到最合适的状态管理方案。
一、状态分类:Ephemeral vs App State
Flutter 官方文档将状态分为两类:
| 类型 | 中文名 | 范围 | 典型例子 | 推荐方案 |
|---|---|---|---|---|
| Ephemeral State | 临时/局部状态 | 单个 Widget 或其子树 | 当前 Tab 索引、动画进度、表单输入值 | StatefulWidget / setState |
| App State | 应用/全局状态 | 跨多个页面共享 | 登录用户信息、购物车内容、主题设置 | Provider / Riverpod / BLoC / Redux |
// 临时状态:BottomNavigationBar 当前索引
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [HomePage(), SearchPage(), ProfilePage()],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
items: [...],
),
);
}
}
一句话总结:临时状态用 setState,全局状态用状态管理库——不需要为了用而用,过度设计同样有害。
二、setState:最简单的状态管理
2.1 基础用法
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _increment() => setState(() => _counter++);
void _decrement() => setState(() => _counter--);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('计数器')),
body: Center(child: Text('$_counter', style: TextStyle(fontSize: 48))),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(onPressed: _increment, child: Icon(Icons.add)),
SizedBox(height: 8),
FloatingActionButton(onPressed: _decrement, child: Icon(Icons.remove)),
],
),
);
}
}
2.2 setState 的局限
// 问题 1:状态提升导致难以维护
class Parent extends StatefulWidget {
@override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int count = 0; // 仅为 ChildB 使用,但不得不放在 Parent
String text = ''; // 仅为 ChildA 使用
bool toggle = false; // 仅为 ChildC 使用
@override
Widget build(BuildContext context) {
return Column(children: [
ChildA(text: text, onChanged: (v) => setState(() => text = v)),
ChildB(count: count, onIncrement: () => setState(() => count++)),
ChildC(toggle: toggle, onToggle: () => setState(() => toggle = !toggle)),
]);
}
}
// 问题 2:跨页面共享状态困难
// PageA 更新了数据,PageB 无法感知,需要路由回传或全局变量
一句话总结:setState 适合简单场景,但当状态需要跨组件、跨页面共享时,就必须引入专门的状态管理方案。
三、Provider:官方推荐入门方案
Provider 是 InheritedWidget 的包装器,由 Remi Rousselet 创建,目前是 Google 官方推荐的状态管理入门方案。
3.1 核心概念
// 1.定义 Model(ChangeNotifier)
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
List<Item> get items => List.unmodifiable(_items);
int get count => _items.length;
double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
void add(Item item) {
_items.add(item);
notifyListeners(); // 通知所有监听者重建
}
void remove(String itemId) {
_items.removeWhere((item) => item.id == itemId);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}
// 2.在 Widget 树顶部提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
// 3.在子页面中读取和监听状态
class CartPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 监听整个 Model 变化
var cart = context.watch<CartModel>();
return Scaffold(
appBar: AppBar(title: Text('购物车 (${cart.count})')),
body: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(cart.items[index].name),
subtitle: Text('¥${cart.items[index].price}'),
),
),
bottomSheet: Padding(
padding: EdgeInsets.all(16),
child: Text('总计: ¥${cart.totalPrice.toStringAsFixed(2)}',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
),
);
}
}
// 4.仅监听特定属性(避免不必要的重建)
class ItemCountBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 使用 Selector 仅监听 count 属性
return Selector<CartModel, int>(
selector: (context, cart) => cart.count,
builder: (context, count, child) => Badge(
label: Text('$count'),
child: Icon(Icons.shopping_cart),
),
);
}
}
// 5.单次读取不监听
class AddToCartButton extends StatelessWidget {
final Item item;
const AddToCartButton({super.key, required this.item});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
context.read<CartModel>().add(item); // 读取不监听
},
child: Text('加入购物车'),
);
}
}
3.2 MultiProvider 与嵌套类型
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthModel()),
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
Provider(create: (_) => ApiService()), // 不变化的服务
StreamProvider<List<Notification>>(
create: (_) => NotificationService().stream,
initialData: [],
),
],
child: MyApp(),
),
);
}
// ProxyProvider:依赖其他 Provider 创建
ChangeNotifierProxyProvider<AuthModel, UserProfileModel>(
create: (context) => UserProfileModel(),
update: (context, auth, previous) {
if (previous == null) return UserProfileModel();
previous.updateUser(auth.currentUser);
return previous;
},
)
一句话总结:Provider 通过简化的 API 暴露了 InheritedWidget 的强大能力,
watch/read/Selector的区分让状态监听既方便又精确。
四、Riverpod:Provider 的精神继承者
Riverpod 同样由 Remi Rousselet 创建,是对 Provider 的全面重构,解决了 Provider 在编译期安全、作用域控制和测试方面的一些根本问题。
4.1 核心特性
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 全局声明 Provider(无需 BuildContext)
final counterProvider = StateProvider<int>((ref) => 0);
final userProvider = FutureProvider<User>((ref) async {
final userId = ref.watch(userIdProvider); // 自动处理依赖变化
return await UserRepository().getUser(userId);
});
final cartProvider = StateNotifierProvider<CartNotifier, CartState>((ref) {
final api = ref.watch(apiServiceProvider); // 自动依赖注入
return CartNotifier(api);
});
// 2. 使用 ConsumerWidget(替代 StatelessWidget)
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
final asyncUser = ref.watch(userProvider);
return Scaffold(
body: asyncUser.when(
data: (user) => Text('Hello, ${user.name}'),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
// 3. StateNotifier:更强大的状态类
class CartState {
final List<Item> items;
final bool isLoading;
final String? error;
const CartState({this.items = const [], this.isLoading = false, this.error});
double get total => items.fold(0, (s, i) => s + i.price);
CartState copyWith({List<Item>? items, bool? isLoading, String? error}) {
return CartState(
items: items ?? this.items,
isLoading: isLoading ?? this.isLoading,
error: error,
);
}
}
class CartNotifier extends StateNotifier<CartState> {
final ApiService api;
CartNotifier(this.api) : super(const CartState());
Future<void> addItem(Item item) async {
state = state.copyWith(isLoading: true);
try {
await api.addToCart(item);
state = state.copyWith(
items: [...state.items, item],
isLoading: false,
);
} catch (e) {
state = state.copyWith(error: e.toString(), isLoading: false);
}
}
void removeItem(String itemId) {
state = state.copyWith(
items: state.items.where((i) => i.id != itemId).toList(),
);
}
}
4.2 Riverpod Generator(代码生成)
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart'; // 由 build_runner 生成
// @riverpod 自动生成 Provider 声明
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
@riverpod
Future<List<Product>> products(ProductsRef ref, String category) async {
final repository = ref.watch(productRepositoryProvider);
return await repository.getByCategory(category);
}
@riverpod
Stream<List<Message>> chatMessages(ChatMessagesRef ref, String roomId) async* {
final service = ref.watch(chatServiceProvider);
yield* service.messages(roomId);
}
// 使用生成的 Provider(无需全局变量)
class ProductList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 调用生成的 counterProvider
final count = ref.watch(counterProvider);
final productsAsync = ref.watch(productsProvider(category: 'electronics'));
return productsAsync.when(
data: (products) => ListView.builder(...),
loading: () => Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败: $e')),
);
}
}
一句话总结:Riverpod 通过编译期安全、自动依赖追踪和代码生成,将 Flutter 状态管理推向了新的高度,是目前新项目的首选方案。
4.3 Riverpod vs Provider
| 维度 | Provider | Riverpod |
|---|---|---|
| 编译期安全 | ❌ 运行时错误 | ✅ Provider 不存在时编译报错 |
| 作用域 | 依赖 BuildContext | 独立于 BuildContext,可在任何地方使用 |
| 自动释放 | 手动 dispose | ✅ autoDispose 自动回收 |
| Family | Provider +参数笨拙 | ✅ 内置 .family 修改器 |
| 代码生成 | ✅ 支持 | ✅ 更完善的 @riverpod Generator |
| 学习曲线 | 较平缓 | 稍陡,但收益更大 |
五、BLoC / Cubit:事件驱动状态机
BLoC(Business Logic Component)模式由 Google 在 2018 年提出,核心思想是将 UI 与业务逻辑解耦,通过事件(Event)驱动状态转换。
5.1 BLoC 完整实现
import 'package:flutter_bloc/flutter_bloc.dart';
// ===== Event =====
sealed class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}
class CounterResetPressed extends CounterEvent {}
// ===== State =====
class CounterState {
final int count;
final DateTime? lastUpdated;
const CounterState({this.count = 0, this.lastUpdated});
CounterState copyWith({int? count, DateTime? lastUpdated}) {
return CounterState(
count: count ?? this.count,
lastUpdated: lastUpdated ?? this.lastUpdated,
);
}
}
// ===== BLoC =====
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState()) {
on<CounterIncrementPressed>(_onIncrement);
on<CounterDecrementPressed>(_onDecrement);
on<CounterResetPressed>(_onReset);
}
void _onIncrement(CounterIncrementPressed event, Emitter<CounterState> emit) {
emit(state.copyWith(
count: state.count + 1,
lastUpdated: DateTime.now(),
));
}
void _onDecrement(CounterDecrementPressed event, Emitter<CounterState> emit) {
if (state.count > 0) {
emit(state.copyWith(
count: state.count - 1,
lastUpdated: DateTime.now(),
));
}
}
void _onReset(CounterResetPressed event, Emitter<CounterState> emit) {
emit(const CounterState());
}
}
// ===== UI 层 =====
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: CounterView(),
);
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('BLoC Counter')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${state.count}', style: TextStyle(fontSize: 48)),
if (state.lastUpdated != null)
Text('上次更新: ${state.lastUpdated}'),
],
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(CounterIncrementPressed()),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(CounterResetPressed()),
child: Icon(Icons.refresh),
),
],
),
);
}
}
5.2 Cubit:轻量版 BLoC
当不需要事件层时,Cubit 提供了更简洁的 API:
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
void reset() => emit(0);
}
// UI
BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text('$count'),
)
// 调用
context.read<CounterCubit>().increment();
5.3 HydratedBloc:持久化状态
class SettingsCubit extends HydratedCubit<SettingsState> {
SettingsCubit() : super(const SettingsState());
void toggleDarkMode() => emit(state.copyWith(isDarkMode: !state.isDarkMode));
void setLocale(String locale) => emit(state.copyWith(locale: locale));
@override
SettingsState? fromJson(Map<String, dynamic> json) =>
SettingsState.fromJson(json);
@override
Map<String, dynamic>? toJson(SettingsState state) => state.toJson();
}
一句话总结:BLoC 的事件驱动模型非常适合需要追踪完整状态流转历史的场景(如调试、审计、时间旅行),Cubit 则是更轻量的日常选择。
六、Redux:函数式状态管理
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
// ===== State =====
class AppState {
final int counter;
final List<String> todos;
final bool isLoading;
AppState({this.counter = 0, this.todos = const [], this.isLoading = false});
}
// ===== Action =====
class IncrementAction {}
class DecrementAction {}
class AddTodoAction {
final String text;
AddTodoAction(this.text);
}
class SetLoadingAction {
final bool isLoading;
SetLoadingAction(this.isLoading);
}
// ===== Reducer =====
AppState appReducer(AppState state, dynamic action) {
return AppState(
counter: counterReducer(state.counter, action),
todos: todoReducer(state.todos, action),
isLoading: loadingReducer(state.isLoading, action),
);
}
int counterReducer(int state, dynamic action) {
if (action is IncrementAction) return state + 1;
if (action is DecrementAction) return state - 1;
return state;
}
List<String> todoReducer(List<String> state, dynamic action) {
if (action is AddTodoAction) return [...state, action.text];
return state;
}
bool loadingReducer(bool state, dynamic action) {
if (action is SetLoadingAction) return action.isLoading;
return state;
}
// ===== Middleware(异步操作) =====
ThunkAction<AppState> fetchTodos() {
return (Store<AppState> store) async {
store.dispatch(SetLoadingAction(true));
final response = await http.get(Uri.parse('/api/todos'));
final todos = jsonDecode(response.body);
for (var todo in todos) {
store.dispatch(AddTodoAction(todo['text']));
}
store.dispatch(SetLoadingAction(false));
};
}
// ===== UI =====
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState(),
middleware: [thunkMiddleware],
);
runApp(StoreProvider(
store: store,
child: MyApp(),
));
}
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, int>(
converter: (store) => store.state.counter,
builder: (context, count) => Text('$count'),
);
}
}
一句话总结:Redux 严格遵循函数式编程原则,通过不可变状态和纯函数 reducer 保证了状态变化的可预测性,但样板代码较多,适合大型团队和需要强约束的项目。
七、六维度选型对比表
| 维度 | Provider | Riverpod | BLoC | Redux | MobX | setState |
|---|---|---|---|---|---|---|
| 学习曲线 | ⭐⭐ 平缓 | ⭐⭐⭐ 中等 | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ 陡峭 | ⭐⭐⭐ 中等 | ⭐ 最简单 |
| 样板代码 | ⭐⭐ 较少 | ⭐⭐ 较少 | ⭐⭐⭐⭐ 较多 | ⭐⭐⭐⭐⭐ 最多 | ⭐⭐ 较少 | ⭐ 最少 |
| 可测试性 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐ 一般 |
| 性能 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐ 一般 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐ 高 |
| 生态系统 | ⭐⭐⭐⭐⭐ 成熟 | ⭐⭐⭐⭐ 快速增长 | ⭐⭐⭐⭐⭐ 成熟 | ⭐⭐⭐ 较成熟 | ⭐⭐⭐ 较成熟 | — |
| 适用场景 | 中小型项目 | 所有规模新项目 | 大型复杂项目 | 超大型团队 | 响应式偏好 | 简单原型 |
八、真实项目选择建议
小型项目(1-3 个页面,2 人团队)
// 推荐:setState + 少量 ValueNotifier
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
final _isDarkMode = ValueNotifier<bool>(false);
final _notifications = ValueNotifier<bool>(true);
@override
void dispose() {
_isDarkMode.dispose();
_notifications.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
ValueListenableBuilder<bool>(
valueListenable: _isDarkMode,
builder: (context, value, _) => SwitchListTile(
title: Text('深色模式'),
value: value,
onChanged: (v) => _isDarkMode.value = v,
),
),
],
),
);
}
}
中型项目(10+ 页面,3-5 人团队)
// 推荐:Riverpod + @riverpod Generator
// 全局状态使用 StateNotifierProvider
// 页面级状态使用 StateProvider
// 派生状态使用 Provider/FutureProvider
大型项目(50+ 页面,10+ 人团队)
// 推荐:BLoC + Clean Architecture
// - 清晰的 Event/State 分层
// - 完整的测试覆盖
// - 状态变化可追踪(BlocObserver)
// - HydratedBloc 持久化
FAQ
Q1: 一个项目可以混用多种状态管理方案吗?
可以但不推荐。混用 Provider 和 BLoC 会增加心智负担和维护成本。建议团队统一方案,对于遗留代码可以逐步迁移。
Q2: Riverpod 的 ref.watch 和 ref.read 什么时候用哪个?
ref.watch:在 build 方法中监听 provider 变化 → UI 自动重建ref.read:在回调中一次性读取 provider 值 → 不建立监听关系- 绝不要在 build 中使用 ref.read 然后期待 UI 更新
Q3: BLoC 和 Redux 有什么区别?
BLoC 更灵活:事件和状态可以是任意类,reducer 逻辑通过 on
Q4: 为什么 BLoC 中要用 Equatable?
因为 Bloc 使用 == 来检测状态变化。若两个状态对象字段相同但实例不同,没有 Equatable 会误判为不同导致不必要的重建。
class MyState extends Equatable {
final int count;
const MyState(this.count);
@override
List<Object?> get props => [count];
}
Q5: Riverpod 的 Family Provider 是什么?
Family 允许创建带参数的 Provider,类似工厂模式:
final userProvider = FutureProvider.family<User, String>((ref, userId) async {
return await fetchUser(userId);
});
// 使用
ref.watch(userProvider('user-123'));
Q6: 状态管理库会影响 Flutter 性能吗?
不会。状态管理库本身的开销极小(微秒级),真正影响性能的是重建范围。Selector / Consumer / buildWhen 等 API 的存在正是为了控制重建粒度。
Q7: GetX 还能用吗?
GetX 曾因其简洁的语法流行,但其作者对 Flutter 社区的贡献存在争议,且 GetX 的架构设计(全局状态、单例依赖)被认为不够健壮。新项目建议选择 Riverpod 或 BLoC。
相关阅读
- https://plumephp.com/flutter-widgets-layout/ — Widget 体系与布局系统
- https://plumephp.com/flutter-navigation-routing/ — 导航与路由管理
- https://plumephp.com/flutter-async-networking/ — 异步编程与网络通信
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。