Flutter 状态管理完全指南:从 setState 到 Riverpod
本文旨在为你提供一份「终极复习资料」,涵盖 Flutter 中五种主流状态管理方案的原理、完整案例、优缺点对比,以及底层设计模式(观察者模式 vs 依赖收集)的深度解析。
适合场景:你在完成所有学习后,通过这一篇文章回顾全部知识点,无需再翻阅历史对话。
目录
- 引言:声明式 UI 与状态管理的本质
- 观察者模式与依赖收集:两大通知机制
- 方案一:setState —— 原生局部状态
- 方案二:InheritedWidget —— 底层跨组件共享
- 方案三:ChangeNotifier + ListenableBuilder —— 观察者模式实践
- 方案四:Provider —— 官方推荐的封装方案
- 方案五:Riverpod —— 下一代编译安全状态管理
- 横向对比总表
- 选型建议与总结
1. 引言:声明式 UI 与状态管理的本质
Flutter 采用声明式 UI 范式:
UI = f(state)
即:UI 是状态的函数,当状态变化时,UI 需要重新执行 build 方法以反映最新状态。
状态管理的核心难题:
- 状态放在哪里(Widget 内部、全局单例、依赖注入容器)?
- 如何通知依赖者(手动触发、观察者模式、依赖收集)?
- 如何控制更新范围(全量重建、细粒度监听)?
- 生命周期与内存管理(自动 dispose、手动清理)?
下文将逐一回答这些问题,通过代码和原理让你彻底理解每种方案的优劣。
2. 观察者模式与依赖收集:两大通知机制
在深入各方案之前,你必须理解两个核心设计模式,因为它们是所有状态管理框架的基础。
2.1 观察者模式(Observer Pattern)
定义:一个对象(被观察者,Subject)维护一组依赖它的对象(观察者,Observer),当自身状态改变时,自动通知所有观察者。
角色:
Subject:提供addListener、removeListener、notifyListeners方法。Observer:提供一个回调函数,注册到 Subject 中。
Flutter 中的典型实现:
// Subject
class CartModel extends ChangeNotifier {
List<String> _items = [];
List<String> get items => _items;
void add(String item) {
_items.add(item);
notifyListeners(); // 通知所有观察者
}
}
// Observer (UI)
class CartBadge extends StatefulWidget {
final CartModel cart;
const CartBadge(this.cart);
@override
_CartBadgeState createState() => _CartBadgeState();
}
class _CartBadgeState extends State<CartBadge> {
@override
void initState() {
super.initState();
widget.cart.addListener(_onCartChanged); // 手动注册
}
void _onCartChanged() => setState(() {});
@override
void dispose() {
widget.cart.removeListener(_onCartChanged); // 手动注销
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('${widget.cart.items.length} items');
}
}
特点:
- ✅ 简单直观,易于理解。
- ❌ 需要手动管理注册/注销,容易造成内存泄漏。
- ❌ 广播式通知:Subject 不知道观察者具体依赖哪部分数据,所有观察者都会收到通知。
2.2 依赖收集(Dependency Collection)
定义:框架自动追踪“在哪个上下文中读取了哪些状态”,当状态变化时,只更新真正依赖该状态的上下文。
工作流程:
- 当代码块(如
build方法)执行时,框架开启一个依赖追踪作用域。 - 每次读取状态(如
ref.watch(provider))时,框架记录“当前代码块依赖于该状态”。 - 当状态变化时,框架重新执行所有依赖该状态的代码块。
Flutter 中的体现:
InheritedWidget+dependOnInheritedWidgetOfExactType是框架级的依赖收集。Provider的context.watch基于此实现。Riverpod的ref.watch是纯 Dart 实现的独立依赖收集系统。
示例(伪代码):
// 依赖收集自动建立关系,无需手动注册
final countProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(countProvider); // 自动记录依赖
return Text('$count');
}
// 当 countProvider 变化时,这个 Widget 自动重建
特点:
- ✅ 自动化,避免手动注册/注销。
- ✅ 精确更新,只重建真正依赖的部分。
- ✅ 组合性强(一个计算属性可依赖多个状态)。
- ❌ 实现复杂,框架需要维护依赖图。
一句话区分:观察者模式是“我主动告诉你我关心你”,依赖收集是“你读取数据时我偷偷记下,数据变了自动找你”。
3. 方案一:setState —— 原生局部状态
3.1 原理
setState 是 StatefulWidget 提供的方法。当调用 setState(() {...}) 时,Flutter 框架会:
- 执行传入的回调(修改状态变量)。
- 标记当前 State 对应的 Element 为 dirty。
- 在下一帧重新调用该 State 的
build方法,重建整个 Widget 子树。
注意:setState 不会细粒度地只更新某个子 Widget,而是重建整个 build 方法返回的 Widget 树(但 Flutter 会通过 canUpdate 和 Widget 的 == 来判断是否真正需要重新创建底层 RenderObject,有一定优化,但 build 方法本身会完整执行)。
3.2 完整案例:计数器 + 切换主题
import 'package:flutter/material.dart';
class SetStateDemo extends StatefulWidget {
@override
_SetStateDemoState createState() => _SetStateDemoState();
}
class _SetStateDemoState extends State<SetStateDemo> {
int _counter = 0;
bool _isDark = false;
void _increment() {
setState(() {
_counter++;
});
}
void _toggleTheme() {
setState(() {
_isDark = !_isDark;
});
}
@override
Widget build(BuildContext context) {
// 每次 setState 都会完整执行 build
print('build 执行');
return MaterialApp(
theme: _isDark ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(title: Text('setState 示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('点击次数: $_counter', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: _increment,
child: Text('增加'),
),
ElevatedButton(
onPressed: _toggleTheme,
child: Text('切换主题'),
),
],
),
),
),
);
}
}
3.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| ✅ 学习成本几乎为零,Flutter 自带 | ❌ 状态共享困难:跨组件需要层层传递回调,代码冗长 |
| ✅ 对于完全独立的局部状态(如一个开关、一个动画值)非常直接 | ❌ 重建范围粗:整个 build 方法重建,若子树庞大且只有叶子节点依赖状态,性能差 |
| ✅ 无需引入任何第三方库 | ❌ 业务逻辑与 UI 耦合:修改状态的代码写在 Widget 中,难以测试 |
| ✅ 生命周期明确,容易理解 | ❌ 缺乏跨页面共享能力:两个不同路由的页面无法直接共享状态 |
❌ 手动管理资源:如控制器、流订阅需在 dispose 中清理 |
3.4 适用场景
- 极简单的页面内部临时状态(如按钮的 loading 状态、当前选中的 tab 索引)。
- 个人项目、原型开发、学习 Flutter 基础。
- 绝对不要用于跨组件或跨页面的状态共享。
4. 方案二:InheritedWidget —— 底层跨组件共享
4.1 原理
InheritedWidget 是 Flutter 框架层提供的数据共享基类。
- 它在 Widget 树中从上往下传递数据。
- 子 Widget 通过
context.dependOnInheritedWidgetOfExactType<T>()获取数据,并自动建立依赖。 - 当
InheritedWidget的updateShouldNotify返回true时,所有依赖它的子 Widget 会被标记为需要重建。
关键点:数据本身不存储在 InheritedWidget 中,通常由外层 StatefulWidget 的 State 持有,并通过重建 InheritedWidget 来触发更新。
4.2 完整案例:购物车 + 筛选 + 订单(基于你历史对话中的代码精简)
import 'package:flutter/material.dart';
// --- Model ---
class CartItem {
final String id;
final String name;
final double price;
int quantity;
CartItem({required this.id, required this.name, required this.price, this.quantity = 1});
}
// --- InheritedWidget 定义 ---
class _CartScope extends InheritedWidget {
final List<CartItem> items;
final void Function(String id) onRemove;
final void Function(CartItem) onAdd;
const _CartScope({
required this.items,
required this.onRemove,
required this.onAdd,
required Widget child,
}) : super(child: child);
static _CartScope of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_CartScope>()!;
}
@override
bool updateShouldNotify(_CartScope old) => items != old.items;
}
class _ThemeScope extends InheritedWidget {
final bool isDark;
final VoidCallback toggleTheme;
const _ThemeScope({required this.isDark, required this.toggleTheme, required Widget child}) : super(child: child);
static _ThemeScope of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<_ThemeScope>()!;
@override
bool updateShouldNotify(_ThemeScope old) => isDark != old.isDark;
}
// --- 主页面 StatefulWidget 持有状态 ---
class InheritedDemoPage extends StatefulWidget {
@override
_InheritedDemoPageState createState() => _InheritedDemoPageState();
}
class _InheritedDemoPageState extends State<InheritedDemoPage> {
List<CartItem> _cartItems = [];
bool _isDark = false;
void _addToCart(CartItem item) {
setState(() {
final index = _cartItems.indexWhere((i) => i.id == item.id);
if (index >= 0) {
_cartItems[index].quantity++;
} else {
_cartItems.add(item);
}
});
}
void _removeFromCart(String id) {
setState(() {
_cartItems.removeWhere((i) => i.id == id);
});
}
void _toggleTheme() {
setState(() {
_isDark = !_isDark;
});
}
@override
Widget build(BuildContext context) {
// 嵌套 InheritedWidget
return _ThemeScope(
isDark: _isDark,
toggleTheme: _toggleTheme,
child: _CartScope(
items: _cartItems,
onAdd: _addToCart,
onRemove: _removeFromCart,
child: MaterialApp(
theme: _isDark ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(title: Text('InheritedWidget 示例')),
body: _Body(),
),
),
),
);
}
}
// --- 子组件,通过 InheritedWidget 获取数据 ---
class _Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cart = _CartScope.of(context);
final theme = _ThemeScope.of(context);
final itemCount = cart.items.fold(0, (sum, item) => sum + item.quantity);
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('购物车共 $itemCount 件商品'),
ElevatedButton(
onPressed: theme.toggleTheme,
child: Text('切换主题'),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (_, i) {
final item = cart.items[i];
return ListTile(
title: Text(item.name),
subtitle: Text('¥${item.price} × ${item.quantity}'),
trailing: IconButton(
icon: Icon(Icons.remove_shopping_cart),
onPressed: () => cart.onRemove(item.id),
),
);
},
),
),
ElevatedButton(
onPressed: () {
cart.onAdd(CartItem(id: '1', name: '商品1', price: 10.0));
},
child: Text('添加示例商品'),
),
],
);
}
}
4.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| ✅ 框架原生,无额外依赖 | ❌ 样板代码极多:每个共享数据都需要单独定义 InheritedWidget、of 方法、updateShouldNotify |
✅ 自动依赖收集(通过 dependOnInheritedWidgetOfExactType) | ❌ 依赖粒度粗:整个 InheritedWidget 任何一个字段变化,所有依赖它的 Widget 都会重建 |
| ✅ 子 Widget 可以方便地获取数据,无需逐层传递 | ❌ 必须配合 StatefulWidget:数据本身需要 StatefulWidget 持有并通过 setState 触发重建 |
❌ 多层嵌套难以维护:如果有多个共享数据(如主题、购物车、用户信息),需要写多个 _XxxScope 嵌套 | |
| ❌ updateShouldNotify 容易写错:比较引用还是字段?浅比较可能导致不必要的重建或漏更新 | |
| ❌ 无法自动 dispose:需要自己管理监听器或控制器 |
4.4 适用场景
- 学习 Flutter 底层原理,理解依赖收集机制。
- 极简单的全局数据共享,如主题、语言设置(但通常直接用
Theme和Locale即可)。 - 生产项目不推荐,因为 Provider 和 Riverpod 提供了更优雅的封装。
5. 方案三:ChangeNotifier + ListenableBuilder —— 观察者模式实践
5.1 原理
ChangeNotifier 是 Flutter 提供的观察者模式实现:
- 继承它,在数据变化时调用
notifyListeners()。 - UI 通过
ListenableBuilder(以前叫AnimatedBuilder)监听ChangeNotifier,当notifyListeners触发时,ListenableBuilder的builder会重建。
优势:状态逻辑与 UI 解耦,Model 可以单独测试。
劣势:仍需手动传递 ChangeNotifier 实例给子组件,或者配合 Provider 使用。
5.2 完整案例:Todo 列表
import 'package:flutter/material.dart';
// --- Model ---
class Todo {
final String title;
bool completed;
Todo(this.title, {this.completed = false});
}
class TodoModel extends ChangeNotifier {
final List<Todo> _todos = [];
List<Todo> get todos => List.unmodifiable(_todos);
void addTodo(String title) {
_todos.add(Todo(title));
notifyListeners(); // 通知 UI
}
void toggleTodo(int index) {
_todos[index].completed = !_todos[index].completed;
notifyListeners();
}
void removeTodo(int index) {
_todos.removeAt(index);
notifyListeners();
}
}
// --- 页面 ---
class ChangeNotifierDemo extends StatelessWidget {
final TodoModel todoModel = TodoModel(); // 手动创建实例
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('ChangeNotifier + ListenableBuilder')),
body: Column(
children: [
// 输入框和添加按钮
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: TextEditingController(),
decoration: InputDecoration(hintText: '输入待办'),
onSubmitted: (value) {
if (value.isNotEmpty) todoModel.addTodo(value);
},
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
// 实际中应该获取输入框内容,这里简化
},
),
],
),
),
// 使用 ListenableBuilder 监听 todoModel
Expanded(
child: ListenableBuilder(
listenable: todoModel,
builder: (context, child) {
final todos = todoModel.todos;
if (todos.isEmpty) {
return Center(child: Text('暂无待办'));
}
return ListView.builder(
itemCount: todos.length,
itemBuilder: (_, i) {
final todo = todos[i];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.completed,
onChanged: (_) => todoModel.toggleTodo(i),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => todoModel.removeTodo(i),
),
);
},
);
},
),
),
],
),
),
);
}
}
补充:也可以使用 ValueNotifier + ValueListenableBuilder 管理单个值,例如:
final counter = ValueNotifier(0);
ValueListenableBuilder(
valueListenable: counter,
builder: (_, value, __) => Text('$value'),
);
counter.value++; // 自动触发重建
5.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| ✅ 状态与 UI 分离,易于测试 | ❌ 仍需要手动传递 Model 实例:深层组件难以获取(可通过构造传参或搭配 InheritedWidget/Provider) |
| ✅ 轻量,无第三方依赖 | ❌ 需要手动 dispose:容易遗忘导致内存泄漏(除非配合 Provider) |
| ✅ 适合单页面内的多个组件共享 | ❌ 广播式通知:Model 中任何一个字段变化,所有 ListenableBuilder 都会重建,需要手动拆分 Model 来优化 |
✅ ListenableBuilder 可以精细控制重建范围(例如只监听一部分) | ❌ 缺乏依赖注入:跨页面共享困难 |
5.4 适用场景
- 中小型应用,单页面内有多个组件需要共享状态。
- 搭配
Provider使用,作为底层 Model(因为 Provider 的ChangeNotifierProvider就是基于此)。 - 需要轻量级解决方案,不想引入复杂框架。
6. 方案四:Provider —— 官方推荐的封装方案
6.1 原理
Provider 是对 InheritedWidget + ChangeNotifier 的高层封装:
- 依赖注入:通过
ChangeNotifierProvider将 Model 注入 Widget 树,子组件通过context.watch<T>()获取并自动建立依赖。 - 生命周期管理:
ChangeNotifierProvider自动在 Widget 销毁时调用ChangeNotifier.dispose。 - 性能优化:提供
Selector和Consumer,可以精细控制重建范围。 - 组合性:
MultiProvider避免多层嵌套。
核心机制:
Provider内部创建了一个InheritedProvider(继承自InheritedWidget)。context.watch<T>()调用dependOnInheritedWidgetOfExactType注册依赖,并返回存储的 T 实例。- 当 T(如
CartModel)调用notifyListeners时,ChangeNotifierProvider监听到变化,触发重建InheritedWidget,进而重建所有依赖它的 Widget。
6.2 完整案例:购物车(基于你历史对话中的 Provider 版本精简)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// --- Model (ChangeNotifier) ---
class CartItem {
final String id;
final String name;
final double price;
int quantity;
CartItem({required this.id, required this.name, required this.price, this.quantity = 1});
}
class CartModel extends ChangeNotifier {
final List<CartItem> _items = [];
List<CartItem> get items => List.unmodifiable(_items);
int get totalQuantity => _items.fold(0, (sum, i) => sum + i.quantity);
double get totalPrice => _items.fold(0.0, (sum, i) => sum + i.price * i.quantity);
void add(CartItem item) {
final idx = _items.indexWhere((i) => i.id == item.id);
if (idx >= 0) {
_items[idx].quantity++;
} else {
_items.add(item);
}
notifyListeners();
}
void remove(String id) {
_items.removeWhere((i) => i.id == id);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}
class UserModel extends ChangeNotifier {
String _name = 'Guest';
String get name => _name;
void setName(String newName) {
_name = newName;
notifyListeners();
}
}
// --- 顶层 Provider 注册 ---
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
// --- 使用 Provider 的页面 ---
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Provider 购物车')),
body: Column(
children: [
// 状态栏(依赖 CartModel)
Consumer<CartModel>(
builder: (_, cart, __) => Container(
padding: EdgeInsets.all(16),
color: Colors.amber.shade100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('商品总数: ${cart.totalQuantity}'),
Text('总价: ¥${cart.totalPrice.toStringAsFixed(2)}'),
],
),
),
),
// 用户名显示(依赖 UserModel,并且使用 Selector 优化)
Selector<UserModel, String>(
selector: (_, user) => user.name,
builder: (_, name, __) => Padding(
padding: EdgeInsets.all(8),
child: Text('当前用户: $name', style: TextStyle(fontSize: 18)),
),
),
// 修改用户名按钮(使用 read 不重建)
ElevatedButton(
onPressed: () {
final newName = 'Alice';
context.read<UserModel>().setName(newName);
},
child: Text('改为 Alice'),
),
// 商品列表
Expanded(
child: Consumer<CartModel>(
builder: (_, cart, __) {
// 模拟商品数据
final products = [
CartItem(id: '1', name: '苹果', price: 5.0),
CartItem(id: '2', name: '香蕉', price: 3.0),
];
return ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) {
final p = products[i];
final existing = cart.items.firstWhere((item) => item.id == p.id, orElse: () => null);
return ListTile(
title: Text(p.name),
trailing: existing == null
? ElevatedButton(onPressed: () => cart.add(p), child: Text('加入'))
: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(onPressed: () => cart.remove(p.id), icon: Icon(Icons.remove)),
Text('${existing.quantity}'),
IconButton(onPressed: () => cart.add(p), icon: Icon(Icons.add)),
],
),
);
},
);
},
),
),
],
),
);
}
}
6.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| ✅ 解决了 InheritedWidget 的样板代码:几行代码完成注册和消费 | ❌ 强依赖 BuildContext:只能在 Widget 的 build 方法及某些异步回调中使用(需确保 context 有效) |
✅ 自动 dispose:ChangeNotifierProvider 自动调用 dispose | ❌ 运行时错误风险:忘记在顶层提供 Provider 会导致 ProviderNotFoundException(编译无法检查) |
✅ 细粒度控制:通过 Selector、Consumer、context.select 可以只监听部分数据 | ❌ 同类型 Provider 冲突:Widget 树中如果有多个同类型的 Provider,watch 只会获取最近的,容易出错 |
| ✅ 官方推荐,生态丰富,文档完善 | ❌ ProxyProvider 复杂:依赖多个 Provider 构建新值时,代码可读性下降 |
| ✅ 易于测试(可模拟 Provider) | ❌ 性能优化依赖开发者主动使用 Selector:直接 watch 整个 Model 会导致粗粒度重建 |
✅ 支持多种 Provider 类型:FutureProvider、StreamProvider、ValueProvider | ❌ 不能脱离 Flutter 使用:因为依赖 BuildContext |
6.4 适用场景
- 当前生产项目的主流选择,尤其适合中小型团队。
- 需要与现有
ChangeNotifier代码集成。 - 对编译时安全要求不高,追求快速开发。
7. 方案五:Riverpod —— 下一代编译安全状态管理
7.1 原理
Riverpod 是 Provider 作者 Remi Rousselet 的新作,完全从零构建,不依赖 Flutter 的 InheritedWidget。
- 核心架构:
ProviderContainer:存储所有 Provider 的状态,是独立的 Dart 对象。Ref:提供watch、read、listen等方法,作为 UI 与容器的桥梁。Provider定义:全局声明,但状态存储在容器中,而非全局单例。
- 依赖收集:通过
ref.watch自动记录依赖,状态变化时精确重建。 - 编译安全:Provider 是对象,不是类型,不会出现“找不到 Provider”的错误。
- 多种 Provider:
StateProvider:简单可变状态(如 int、String)。Provider:只读派生状态。NotifierProvider:复杂状态管理(替代ChangeNotifier)。FutureProvider、StreamProvider:异步数据。
- 自动 dispose:基于引用计数,不再被监听时自动销毁状态。
7.2 完整案例:购物车 + 筛选(基于你历史对话中的 Riverpod 代码精简)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// --- 数据模型 ---
class CartItem {
final String id;
final String name;
final double price;
final int quantity;
const CartItem({required this.id, required this.name, required this.price, this.quantity = 1});
CartItem copyWith({int? quantity}) => CartItem(id: id, name: name, price: price, quantity: quantity ?? this.quantity);
}
// --- Providers ---
// 简单状态:分类筛选
final categoryProvider = StateProvider<String>((ref) => '全部');
// 只读派生状态:筛选后的商品列表(依赖 categoryProvider)
final filteredProductsProvider = Provider<List<CartItem>>((ref) {
final category = ref.watch(categoryProvider);
final allProducts = [
CartItem(id: '1', name: '苹果', price: 5.0),
CartItem(id: '2', name: '香蕉', price: 3.0),
CartItem(id: '3', name: '橘子', price: 4.0),
];
if (category == '全部') return allProducts;
// 假设商品有 category 字段,这里简单用名称模拟
return allProducts.where((p) => p.name.contains(category)).toList();
});
// 复杂状态:购物车(使用 Notifier)
final cartProvider = NotifierProvider<CartNotifier, List<CartItem>>(CartNotifier.new);
class CartNotifier extends Notifier<List<CartItem>> {
@override
List<CartItem> build() => [];
void add(CartItem product) {
final idx = state.indexWhere((item) => item.id == product.id);
if (idx >= 0) {
state = [
for (int i = 0; i < state.length; i++)
if (i == idx) state[i].copyWith(quantity: state[i].quantity + 1) else state[i],
];
} else {
state = [...state, product];
}
}
void remove(String id) {
state = state.where((item) => item.id != id).toList();
}
void clear() => state = [];
}
// 派生状态:购物车总数量
final cartTotalQuantityProvider = Provider<int>((ref) {
return ref.watch(cartProvider).fold(0, (sum, item) => sum + item.quantity);
});
// --- UI ---
void main() => runApp(ProviderScope(child: MyApp())); // ProviderScope 是 Riverpod 的根容器
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(home: RiverpodDemo());
}
class RiverpodDemo extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final filtered = ref.watch(filteredProductsProvider);
final cart = ref.watch(cartProvider);
final totalQty = ref.watch(cartTotalQuantityProvider);
return Scaffold(
appBar: AppBar(title: Text('Riverpod 购物车')),
body: Column(
children: [
// 购物车状态栏
Container(
padding: EdgeInsets.all(16),
color: Colors.teal.shade50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('购物车共 $totalQty 件商品'),
Text('总价: ¥${cart.fold(0.0, (sum, i) => sum + i.price * i.quantity).toStringAsFixed(2)}'),
],
),
),
// 分类选择(使用 StateProvider)
Padding(
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: ['全部', '苹果', '香蕉'].map((cat) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: ChoiceChip(
label: Text(cat),
selected: ref.watch(categoryProvider) == cat,
onSelected: (_) => ref.read(categoryProvider.notifier).state = cat,
),
);
}).toList(),
),
),
// 商品列表
Expanded(
child: ListView.builder(
itemCount: filtered.length,
itemBuilder: (_, i) {
final product = filtered[i];
// 注意:这里直接 watch 整个 cartProvider,会导致每个商品 item 重建,但可以通过 Consumer 优化
final cartItem = ref.watch(cartProvider).firstWhere((item) => item.id == product.id, orElse: () => null);
return ListTile(
title: Text(product.name),
subtitle: Text('¥${product.price}'),
trailing: cartItem == null
? ElevatedButton(onPressed: () => ref.read(cartProvider.notifier).add(product), child: Text('加入'))
: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(onPressed: () => ref.read(cartProvider.notifier).remove(product.id), icon: Icon(Icons.remove)),
Text('${cartItem.quantity}'),
IconButton(onPressed: () => ref.read(cartProvider.notifier).add(product), icon: Icon(Icons.add)),
],
),
);
},
),
),
],
),
);
}
}
优化版本:使用 Consumer 让每个商品 item 独立监听购物车,避免整个列表重建(性能优化):
// 替换商品列表中的 itemBuilder
itemBuilder: (_, i) {
final product = filtered[i];
return Consumer(
builder: (_, ref, __) {
final cartItem = ref.watch(cartProvider).firstWhere((item) => item.id == product.id, orElse: () => null);
return ListTile(...); // 同上方
},
);
}
7.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| ✅ 编译安全:Provider 是对象,不会出现运行时“找不到 Provider”的错误 | ❌ 学习曲线较陡:需要理解 ref、ProviderScope、Notifier 等概念 |
| ✅ 不依赖 BuildContext:可以在任何 Dart 代码中使用(Service、Repository、main 函数) | ❌ 样板代码略增:每个复杂状态需要定义 Notifier 类(但比 ChangeNotifier 更规范) |
| ✅ 自动 dispose:基于引用计数,无需手动清理 | ❌ 全局声明可能引起误解:初学者可能误以为状态是单例,实际上每个 ProviderScope 有独立容器 |
✅ 细粒度重建天然支持:ref.watch 自动追踪依赖,并提供 .select 方法进一步过滤 | ❌ 生态较新:虽然稳定,但第三方库集成不如 Provider 广泛 |
| ✅ 强组合性:Provider 可以随意 watch 其他 Provider | ❌ 性能优化需要理解依赖追踪机制:错误使用 read 代替 watch 会导致 UI 不更新 |
✅ 易于测试:通过 ProviderContainer 隔离测试,无需 WidgetTester | ❌ 调试工具不如 Provider 成熟(但正在改进) |
✅ 支持异步:FutureProvider、StreamProvider 开箱即用 |
7.4 适用场景
- 新项目首选,尤其需要复杂状态组合、跨页面共享、高可测试性。
- 需要支持非 Flutter 环境(如 Dart CLI 脚本、后台任务)的状态管理。
- 团队愿意接受新的设计模式,追求编译时安全和长期维护性。
- 大型项目,多个模块独立开发,需要避免 Provider 的运行时错误。
8. 横向对比总表
| 特性 | setState | InheritedWidget | ChangeNotifier + ListenableBuilder | Provider | Riverpod |
|---|---|---|---|---|---|
| 学习曲线 | 低 | 高 | 低 | 中 | 中高 |
| 代码量(实现相同功能) | 少(但共享困难) | 很多 | 中(需手动传递实例) | 少 | 中 |
| 跨组件共享 | 困难(需回调传递) | 可(嵌套麻烦) | 需配合 InheritedWidget 或 Provider | 容易 | 容易 |
| 依赖 BuildContext | 是 | 是 | 否(但获取实例需传递) | 是 | 否 |
| 编译时安全 | 无 | 无 | 无 | 部分(类型安全,但有运行时错误) | 完全 |
| 自动 dispose | 需手动 | 需手动 | 需手动 | 自动 | 自动 |
| 细粒度更新 | 需手动拆分 Widget | 需手动拆分 Widget | 需手动拆分 Model + 多个 ListenableBuilder | 通过 Selector / Consumer | 通过 ref.watch + select |
| 支持异步 | 手动 | 手动 | 手动 | FutureProvider | 原生 FutureProvider / StreamProvider |
| 测试难度 | 中 | 高 | 中 | 中 | 低(容器隔离) |
| 依赖注入 | 无 | 有(通过类型) | 无 | 有(通过 BuildContext) | 有(通过 ProviderContainer) |
| 是否可脱离 Flutter | 否 | 否 | 可(但 UI 仍需 Flutter) | 否 | 是(核心是纯 Dart) |
| 典型应用规模 | 单页面 < 1k 行 | 学习/极简单 | 小项目,单页面 | 中小型项目 | 中大型项目 |
9. 选型建议与总结
9.1 项目选型决策树
是否需要跨组件/跨页面共享?
├── 否 → 状态是否复杂(多个字段、异步)?
│ ├── 否 → setState 足够
│ └── 是 → ChangeNotifier + ListenableBuilder(单页面内)
└── 是 → 项目规模?
├── 小型(< 10k 行,团队 < 5 人) → Provider
├── 中大型(> 10k 行,多团队协作)
│ ├── 对编译安全要求高,愿意学习新概念 → Riverpod
│ └── 团队已熟悉 Provider,不想迁移 → Provider(但要规范化使用 Selector)
└── 需要运行在非 Flutter 环境(如 Dart CLI) → 只能用 Riverpod
9.2 总结
| 方案 | 一句话评价 |
|---|---|
| setState | 基础工具,适用于局部状态,跨组件即痛苦。 |
| InheritedWidget | 框架底层,学习价值高,实际生产中几乎不用。 |
| ChangeNotifier + ListenableBuilder | 轻量观察者模式,适合单页面内多组件共享,但需手动传递实例。 |
| Provider | 当前最流行,封装完善,依赖 Context,存在运行时风险但可接受。 |
| Riverpod | 下一代方案,编译安全,解耦 Context,推荐新项目使用。 |
9.3 学习路径建议
如果你是从零开始学习 Flutter 状态管理,建议按以下顺序:
- setState:理解声明式 UI 和局部状态。
- InheritedWidget:理解 Flutter 底层的依赖注入机制。
- ChangeNotifier:理解观察者模式与状态分离。
- Provider:理解如何封装 InheritedWidget 并提供便捷 API。
- Riverpod:理解如何完全脱离 Context,实现编译安全。
你已经走完了这条路径,现在你对每个方案的工作原理、优缺点、适用场景已经有了深刻的认知。在实际开发中,请根据项目需求、团队熟悉度和长期维护成本做出合理选择。
最后记住:没有“最好”的状态管理,只有“最适合当前项目”的状态管理。性能、可维护性、学习成本、生态支持四者需要权衡。
本文档总字数约 1.2 万字,涵盖了五种状态管理方案的完整原理、代码案例和对比分析,可作为你的终极复习资料。