08_状态管理
Flutter 状态管理方案介绍
Flutter 状态管理方案对比与实践,从 setState 到 Provider、Riverpod。
方案选型
| 方案 | 对应 Web | 适用场景 | 学习成本 |
|---|---|---|---|
setState |
useState / data() |
单组件内部状态 | ⭐ |
| 状态提升 | props + emit | 父子组件共享 | ⭐ |
| Provider | Context API / Pinia | 中大型 App,官方推荐 | ⭐⭐ |
| Riverpod | Zustand / Jotai | 大型 App,类型安全 | ⭐⭐⭐ |
| GetX | 全家桶 | 快速开发 | ⭐⭐ |
| BLoC | Redux | 大型团队,强约束 | ⭐⭐⭐⭐ |
setState(局部状态)
适合单组件内,不需要跨组件共享的状态:
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() { _count++; }); // 包裹修改,触发重建
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('$_count', style: const TextStyle(fontSize: 48)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
}
setState 常见坑:
// ❌ async 后 widget 可能已卸载
Future<void> loadData() async {
final data = await fetchApi();
setState(() { _data = data; }); // 可能报错
}
// ✅ 先检查 mounted
Future<void> loadData() async {
final data = await fetchApi();
if (mounted) setState(() { _data = data; });
}
状态提升(父子组件共享)
将状态提升到最近的公共父组件,类似 Vue 的 props + emit:
// 父组件持有状态
class _ParentState extends State<ParentWidget> {
bool _isOn = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
ToggleSwitch(
value: _isOn,
onChanged: (v) => setState(() => _isOn = v), // 回调修改父状态
),
Text(_isOn ? '开启' : '关闭'),
],
);
}
}
// 子组件通过参数接收(类似 Vue props + emit)
class ToggleSwitch extends StatelessWidget {
final bool value;
final ValueChanged<bool> onChanged; // 回调函数类型
const ToggleSwitch({super.key, required this.value, required this.onChanged});
@override
Widget build(BuildContext context) {
return Switch(value: value, onChanged: onChanged);
}
}
Provider(全局状态,官方推荐)
# pubspec.yaml
dependencies:
provider: ^6.1.0
第一步:定义数据模型
// 类似 Pinia 的 defineStore
class CartModel extends ChangeNotifier {
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
int get count => _items.length;
void add(String item) {
_items.add(item);
notifyListeners(); // 通知所有监听者重建(类似 Vue 响应式触发)
}
void remove(String item) {
_items.remove(item);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}
第二步:注入 Provider(类似 Vue app.use(pinia))
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: const MyApp(),
),
);
}
第三步:在组件中消费
class CartPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context.watch:订阅变化,CartModel 更新时组件重建
final cart = context.watch<CartModel>();
return Column(
children: [
Text('购物车 (${cart.count} 件)'),
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (ctx, i) => ListTile(
title: Text(cart.items[i]),
trailing: IconButton(
icon: const Icon(Icons.delete),
// context.read:仅读取,不订阅(用于事件处理)
onPressed: () => context.read<CartModel>().remove(cart.items[i]),
),
),
),
),
ElevatedButton(
onPressed: () => context.read<CartModel>().clear(),
child: const Text('清空'),
),
],
);
}
}
Provider 方法速查:
| 方法 | 场景 | 对比 Vue/React |
|---|---|---|
context.watch<T>() |
build 中读取并订阅 | computed / useStore() |
context.read<T>() |
事件处理中读取(不订阅) | store.action() |
context.select<T,R>((t) => t.x) |
只订阅部分字段,减少重建 | computed(() => store.x) |
Consumer<T> |
精细控制重建范围 | 局部 <template> |
Consumer 精细控制重建范围
// ❌ 整个 build 因 CartModel 变化而重建(AppBar 也被重建了)
Widget build(BuildContext context) {
final cart = context.watch<CartModel>();
return Scaffold(
appBar: AppBar(title: const Text('商店')), // 不需要 cart,但也重建了
body: CartList(cart: cart),
);
}
// ✅ 只让需要更新的部分重建
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBar(title: Text('商店')), // const,永不重建
body: Consumer<CartModel>(
builder: (ctx, cart, child) => CartList(cart: cart),
),
);
}
Riverpod(进阶方案)
dependencies:
flutter_riverpod: ^2.4.0
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 定义 Provider(全局,不在 Widget 内)
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
(ref) => CounterNotifier(),
);
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// 根组件包裹 ProviderScope
void main() {
runApp(const ProviderScope(child: MyApp()));
}
// 组件继承 ConsumerWidget
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // 订阅
return Column(
children: [
Text('$count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Text('+1'),
),
],
);
}
}
Riverpod vs Provider:
| Provider | Riverpod | |
|---|---|---|
| 类型安全 | 一般 | ✅ 编译期检查 |
| 无需 BuildContext | ❌ | ✅ |
| 测试友好 | 一般 | ✅ 方便 mock |
| 推荐场景 | 中型项目 | 大型项目 |