Provider.of与context.watch_Flutter在鸿蒙平台状态获取方式全解析


作者:付文龙(红目香薰)
仓库地址:https://gitcode.com/feng8403000/FlutterfromBeginnertoAdvancedForHarmonyOS.git
联系邮箱:372699828@qq.com
一、简介
除了Consumer和Selector,Provider还提供了通过context获取Provider实例的方式,包括Provider.of、context.watch、context.read和context.select。这些方式在不同场景下各有优势,掌握它们的使用方法是灵活运用Provider的关键。
1.1 为什么需要多种获取方式
在实际开发中,不同的场景需要不同的状态获取方式:
- 监听模式:在build方法中获取数据,需要监听变化
- 非监听模式:在事件回调中修改数据,不需要监听变化
- 选择性监听:只关注部分数据,避免不必要的重建
Provider提供了多种获取方式来满足这些需求。
1.2 获取方式对比
| 方式 | 监听模式 | 触发重建 | 适用场景 |
|---|---|---|---|
| Provider.of(context) | 是 | 是 | build方法中获取数据 |
| Provider.of(context, listen: false) | 否 | 否 | 事件回调中修改数据 |
| context.watch() | 是 | 是 | build方法中获取数据(推荐) |
| context.read() | 否 | 否 | 事件回调中修改数据(推荐) |
| context.select<T, R>() | 条件 | 条件 | 只关注部分数据 |
二、Provider.of基本用法
2.1 方式1:监听模式(默认)
final cart = Provider.of<CartProvider>(context);
// 当CartProvider变化时,当前Widget会重建
默认情况下,Provider.of<T>(context)会监听Provider的变化,当数据变化时,当前Widget会重建。
2.2 方式2:非监听模式
final cart = Provider.of<CartProvider>(context, listen: false);
// 获取Provider实例,但不监听变化
// 常用于在事件处理中修改状态
通过设置listen: false,可以获取Provider实例但不监听变化。这种方式常用于事件回调中修改状态。
2.3 完整示例
class CartPage extends StatelessWidget {
const CartPage({super.key});
Widget build(BuildContext context) {
// 监听模式:获取数据并监听变化
final cart = Provider.of<CartProvider>(context);
return Scaffold(
appBar: AppBar(title: const Text('购物车')),
body: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
subtitle: Text("¥${item.price}"),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
// 非监听模式:修改数据但不监听变化
Provider.of<CartProvider>(context, listen: false)
.removeItem(item.id);
},
),
);
},
),
);
}
}
三、context.watch
3.1 基本用法
context.watch<T>()是Provider.of<T>(context)的简写:
// 等价写法
final cart = context.watch<CartProvider>();
final cart = Provider.of<CartProvider>(context);
3.2 context.watch的特点
| 特性 | 说明 |
|---|---|
| 简写形式 | 代码更简洁 |
| 监听模式 | 默认listen: true |
| 触发重建 | 数据变化时触发当前Widget重建 |
| 使用位置 | 只能在build方法中使用 |
3.3 在build方法中使用
class CartSummary extends StatelessWidget {
const CartSummary({super.key});
Widget build(BuildContext context) {
// 在build方法中获取数据(监听模式)
final cart = context.watch<CartProvider>();
return Text("商品数量: ${cart.itemCount}");
}
}
3.4 在StatelessWidget中使用
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
final cart = context.watch<CartProvider>();
final user = context.watch<UserProvider>();
return Scaffold(
appBar: AppBar(title: Text("欢迎, ${user.name}")),
body: Column(
children: [
Text("购物车商品数量: ${cart.itemCount}"),
Text("购物车总价: ¥${cart.totalPrice}"),
],
),
);
}
}
四、context.read
4.1 基本用法
context.read<T>()是Provider.of<T>(context, listen: false)的简写:
// 等价写法
final cart = context.read<CartProvider>();
final cart = Provider.of<CartProvider>(context, listen: false);
4.2 context.read的特点
| 特性 | 说明 |
|---|---|
| 简写形式 | 代码更简洁 |
| 非监听模式 | listen: false |
| 不触发重建 | 数据变化时不触发当前Widget重建 |
| 使用位置 | 常用于事件处理回调中 |
4.3 在事件回调中使用
class AddToCartButton extends StatelessWidget {
final CartItem item;
const AddToCartButton({super.key, required this.item});
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// 在事件回调中修改数据(非监听模式)
final cart = context.read<CartProvider>();
cart.addItem(item);
},
child: const Text("添加到购物车"),
);
}
}
4.4 在StatefulWidget中使用
class CartPage extends StatefulWidget {
const CartPage({super.key});
State<CartPage> createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('购物车')),
body: Consumer<CartProvider>(
builder: (context, cart, child) {
return ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
// 使用context.read修改数据
context.read<CartProvider>().removeItem(item.id);
},
),
);
},
);
},
),
);
}
}
五、context.select
5.1 基本用法
context.select<T, R>(selector)是Selector的简写形式:
Widget build(BuildContext context) {
final count = context.select<CartProvider, int>(
(provider) => provider.itemCount,
);
return Text("商品数量: $count");
}
5.2 context.select的工作原理
- selector函数:从Provider中提取需要的数据
- 比较新旧数据:使用
==运算符比较新旧数据是否相等 - 决定是否重建:只有数据变化时才触发当前Widget重建
- 复用Widget:数据相同时复用之前的Widget
5.3 选择多个值
class CartSummary extends StatelessWidget {
Widget build(BuildContext context) {
final count = context.select<CartProvider, int>(
(provider) => provider.itemCount,
);
final total = context.select<CartProvider, double>(
(provider) => provider.totalPrice,
);
return Column(
children: [
Text("数量: $count"),
Text("总价: ¥$total"),
],
);
}
}
5.4 选择复杂类型
class UserProfile extends StatelessWidget {
Widget build(BuildContext context) {
final user = context.select<UserProvider, User?>(
(provider) => provider.user,
);
if (user == null) {
return const Text("未登录");
}
return Text("欢迎, ${user.name}");
}
}
六、正确使用方式
6.1 在build方法中获取数据(监听模式)
Widget build(BuildContext context) {
final cart = context.watch<CartProvider>();
return Text("商品数量: ${cart.itemCount}");
}
6.2 在事件回调中修改数据(非监听模式)
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
final cart = context.read<CartProvider>();
cart.addItem(CartItem(id: "1", name: "商品", price: 100));
},
child: const Text("添加商品"),
);
}
6.3 只关注部分数据(选择性监听)
Widget build(BuildContext context) {
final count = context.select<CartProvider, int>(
(provider) => provider.itemCount,
);
return Text("商品数量: $count");
}
七、错误使用方式
7.1 错误:在事件回调中使用watch
onPressed: () {
final cart = context.watch<CartProvider>(); // 警告!
cart.addItem(item);
}
问题:在事件回调中使用watch会导致不必要的监听,可能引发警告。
解决方案:使用context.read代替。
onPressed: () {
final cart = context.read<CartProvider>();
cart.addItem(item);
}
7.2 错误:在build中使用read获取需要监听的数据
Widget build(BuildContext context) {
final cart = context.read<CartProvider>();
return Text("${cart.itemCount}"); // 不会更新!
}
问题:使用read获取数据不会监听变化,导致UI不会更新。
解决方案:使用context.watch代替。
Widget build(BuildContext context) {
final cart = context.watch<CartProvider>();
return Text("${cart.itemCount}");
}
7.3 错误:在initState中使用watch/read
class _MyWidgetState extends State<MyWidget> {
void initState() {
super.initState();
final cart = context.watch<CartProvider>(); // 错误!
}
}
问题:在initState中,context还没有完全初始化,不能使用watch/read。
解决方案:使用didChangeDependencies或addPostFrameCallback。
class _MyWidgetState extends State<MyWidget> {
void didChangeDependencies() {
super.didChangeDependencies();
final cart = context.watch<CartProvider>();
}
}
八、多种获取方式对比
8.1 详细对比表
| 方式 | 监听模式 | 触发重建 | 使用位置 | 代码简洁度 |
|---|---|---|---|---|
| Provider.of(context) | 是 | 是 | build方法 | 中等 |
| Provider.of(context, listen: false) | 否 | 否 | 事件回调 | 中等 |
| context.watch() | 是 | 是 | build方法 | 高 |
| context.read() | 否 | 否 | 事件回调 | 高 |
| context.select<T, R>() | 条件 | 条件 | build方法 | 高 |
| Consumer | 是 | 是 | 任意位置 | 中等 |
| Selector<T, R> | 条件 | 条件 | 任意位置 | 中等 |
8.2 选择指南
是否需要监听数据变化?
├── 是 → 是否需要精确控制重建范围?
│ ├── 是 → 使用Consumer或Selector
│ └── 否 → 使用context.watch或context.select
└── 否 → 使用context.read
8.3 推荐用法
| 场景 | 推荐方式 |
|---|---|
| 在build方法中获取数据 | context.watch() |
| 在事件回调中修改数据 | context.read() |
| 只关注部分数据 | context.select<T, R>() |
| 需要精确控制重建范围 | Consumer或Selector<T, R> |
| 需要访问多个Provider | Consumer2/3/4 |
九、在StatefulWidget中的使用
9.1 initState中不能使用watch/read
在initState方法中,context还没有完全初始化,不能使用watch/read:
class _MyWidgetState extends State<MyWidget> {
void initState() {
super.initState();
// ❌ 错误:不能在initState中使用
// final cart = context.watch<CartProvider>();
}
}
9.2 使用didChangeDependencies
didChangeDependencies方法在context初始化完成后调用,可以使用watch/read:
class _MyWidgetState extends State<MyWidget> {
void didChangeDependencies() {
super.didChangeDependencies();
// ✅ 正确:可以在这里使用
final cart = context.watch<CartProvider>();
}
}
9.3 使用addPostFrameCallback
addPostFrameCallback在Widget构建完成后调用,可以使用watch/read:
class _MyWidgetState extends State<MyWidget> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// ✅ 正确:在Frame构建完成后使用
final cart = context.read<CartProvider>();
cart.loadCart();
});
}
}
9.4 在build方法中使用
在build方法中,可以自由使用watch/read:
class _MyWidgetState extends State<MyWidget> {
Widget build(BuildContext context) {
// ✅ 正确:在build方法中使用watch
final cart = context.watch<CartProvider>();
return Scaffold(
body: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
// ✅ 正确:在事件回调中使用read
context.read<CartProvider>().removeItem(item.id);
},
),
);
},
),
);
}
}
十、实际应用场景
10.1 购物车页面
class CartPage extends StatelessWidget {
const CartPage({super.key});
Widget build(BuildContext context) {
// 使用context.watch获取数据
final cart = context.watch<CartProvider>();
return Scaffold(
appBar: AppBar(title: const Text('购物车')),
body: cart.items.isEmpty
? const Center(child: Text("购物车为空"))
: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(
title: Text(item.name),
subtitle: Text("¥${item.price} x${item.quantity}"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
// 使用context.read修改数据
context.read<CartProvider>()
.updateQuantity(item.id, item.quantity - 1);
},
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
// 使用context.read修改数据
context.read<CartProvider>()
.updateQuantity(item.id, item.quantity + 1);
},
),
],
),
);
},
),
bottomNavigationBar: Container(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 使用context.select只监听totalPrice
Text("总价: ¥${context.select<CartProvider, double>(
(provider) => provider.totalPrice,
).toStringAsFixed(2)}"),
ElevatedButton(
onPressed: cart.items.isEmpty ? null : () {},
child: const Text("结算"),
),
],
),
),
);
}
}
10.2 用户登录页面
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
Widget build(BuildContext context) {
final userProvider = context.watch<UserProvider>();
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Center(
child: userProvider.isLoading
? const CircularProgressIndicator()
: Column(
children: [
TextField(
decoration: const InputDecoration(labelText: '邮箱'),
onChanged: (value) {
// 使用context.read修改数据
context.read<UserProvider>().setEmail(value);
},
),
TextField(
decoration: const InputDecoration(labelText: '密码'),
obscureText: true,
onChanged: (value) {
// 使用context.read修改数据
context.read<UserProvider>().setPassword(value);
},
),
ElevatedButton(
onPressed: () {
// 使用context.read调用方法
context.read<UserProvider>().login();
},
child: const Text("登录"),
),
if (userProvider.error != null)
Text(userProvider.error!, style: const TextStyle(color: Colors.red)),
],
),
),
);
}
}
10.3 主题切换页面
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: ListTile(
title: const Text('深色模式'),
trailing: Switch(
value: context.select<ThemeProvider, bool>(
(provider) => provider.isDark,
),
onChanged: (value) {
context.read<ThemeProvider>().toggleTheme();
},
),
),
);
}
}
十一、性能优化技巧
11.1 使用context.select减少重建
// 优化前:监听整个Provider
final cart = context.watch<CartProvider>();
return Text("${cart.itemCount}");
// 优化后:只监听itemCount
final count = context.select<CartProvider, int>(
(provider) => provider.itemCount,
);
return Text("$count");
11.2 在事件回调中使用context.read
// 正确:使用read不会触发监听
onPressed: () {
context.read<CartProvider>().addItem(item);
}
// 错误:使用watch会触发不必要的监听
onPressed: () {
context.watch<CartProvider>().addItem(item); // 警告!
}
11.3 拆分Widget减少重建范围
// 优化前:单个Widget监听多个数据
class CartPage extends StatelessWidget {
Widget build(BuildContext context) {
final cart = context.watch<CartProvider>();
return Column(
children: [
Text("商品数量: ${cart.itemCount}"),
Text("总价: ¥${cart.totalPrice}"),
CartList(items: cart.items),
],
);
}
}
// 优化后:拆分组件各自监听
class CartPage extends StatelessWidget {
Widget build(BuildContext context) {
return const Column(
children: [
CartCount(), // 内部使用context.select监听itemCount
CartTotal(), // 内部使用context.select监听totalPrice
CartList(), // 内部使用context.watch监听items
],
);
}
}
十二、总结
Provider提供了多种状态获取方式,每种方式适用于不同的场景:
- context.watch():在build方法中获取数据,监听变化
- context.read():在事件回调中修改数据,不监听变化
- context.select<T, R>():只关注部分数据,选择性监听
- Consumer:精确控制重建范围
- Selector<T, R>:选择性监听,提升性能
在使用时,需要注意以下几点:
- 在build方法中使用watch,在事件回调中使用read
- 使用select减少重建范围,只监听需要的数据
- 不要在initState中使用watch/read,使用didChangeDependencies或addPostFrameCallback
- 拆分Widget实现细粒度重建,提升性能
掌握这些状态获取方式,是灵活运用Provider的关键。
更多推荐


所有评论(0)