Provider:ChangeNotifier 与 Consumer 模式
mediumflutterproviderchangenotifierconsumermultiproviderselector
Provider 是什么?
provider 是对 InheritedWidget 的高层封装,由 Remi Rousselet 创建,曾是 Flutter 官方推荐方案。它极大地减少了直接使用 InheritedWidget 的样板代码。
flutter pub add provider
核心三件套
1. ChangeNotifier —— 数据层
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
List<Item> get items => List.unmodifiable(_items);
int get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
void add(Item item) {
_items.add(item);
notifyListeners(); // 通知所有监听者重建
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
}
ChangeNotifier 是 Flutter SDK 内置的 Listenable 实现,调用 notifyListeners() 会通知所有注册的监听者。
2. ChangeNotifierProvider —— 注入层
// 在 Widget 树顶部提供数据
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CartModel(), // 懒创建,首次访问时才初始化
child: MyApp(),
),
);
}
多个 Provider 用 MultiProvider:
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
Provider<ApiClient>(create: (_) => ApiClient()), // 不可变数据用 Provider
],
child: MyApp(),
)
3. Consumer / context.watch —— 消费层
方式一:Consumer(细粒度局部重建)
class CartSummary extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CartModel>(
builder: (context, cart, child) {
// cart 变化时,只有这里 rebuild
return Text('总计: ${cart.totalPrice}');
},
// child 参数:不依赖 cart 的子树,Consumer 不会重建它
// child: ExpensiveWidget(),
);
}
}
方式二:context.watch(整个 Widget rebuild)
class CartBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
final count = context.watch<CartModel>().items.length;
// CartBadge 整个 rebuild(当 CartModel 通知时)
return Badge(count: count);
}
}
方式三:context.read(只读,不订阅)
class AddToCartButton extends StatelessWidget {
final Item item;
@override
Widget build(BuildContext context) {
return ElevatedButton(
// onPressed 里用 read,因为只要触发时获取当前值,不需要订阅
onPressed: () => context.read<CartModel>().add(item),
child: const Text('加入购物车'),
);
}
}
Selector —— 精细控制 rebuild
Consumer 会在 ChangeNotifier 任何 notifyListeners() 时重建。Selector 允许只监听某个字段:
// 只在 items.length 变化时 rebuild(即使 totalPrice 也变了但数量未变时不重建)
Selector<CartModel, int>(
selector: (_, cart) => cart.items.length,
builder: (_, count, __) => Text('已选 $count 件'),
)
ProxyProvider —— 依赖其他 Provider
MultiProvider(
providers: [
Provider<TokenStorage>(create: (_) => TokenStorage()),
// ApiClient 依赖 TokenStorage
ProxyProvider<TokenStorage, ApiClient>(
update: (_, tokenStorage, __) => ApiClient(tokenStorage),
),
// UserRepository 依赖 ApiClient
ProxyProvider<ApiClient, UserRepository>(
update: (_, apiClient, __) => UserRepository(apiClient),
),
],
)
Provider vs ChangeNotifierProvider
| Provider | ChangeNotifierProvider | |
|---|---|---|
| 数据类型 | 任意不可变对象 | ChangeNotifier 实例 |
| 通知方式 | 不需要(数据不变) | notifyListeners() |
| 用途 | 服务、配置 | 业务状态 |
// 不可变数据(如 API Client、Repository)用 Provider
Provider<ApiClient>(create: (_) => ApiClient('https://api.example.com'))
// 可变业务状态用 ChangeNotifierProvider
ChangeNotifierProvider(create: (_) => UserViewModel())
核心工程问题
Q:context.read 和 context.watch 的区别?
watch 建立订阅,Provider 更新时该 Widget rebuild。read 不订阅,只是当时读取一次当前值。在 build 方法中用 watch,在事件处理器(onPressed 等)中用 read。
Q:Provider 的 create 参数和 value 参数的区别?
create: (_) => MyModel() 由 Provider 负责创建和 dispose;value: existingModel 使用已有实例,Provider 不会 dispose 它。如果 Model 在 Provider 外创建(如从数据库恢复),用 value。