在这里插入图片描述
在这里插入图片描述

作者:付文龙(红目香薰)
仓库地址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的工作原理

  1. selector函数:从Provider中提取需要的数据
  2. 比较新旧数据:使用==运算符比较新旧数据是否相等
  3. 决定是否重建:只有数据变化时才触发当前Widget重建
  4. 复用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>:选择性监听,提升性能

在使用时,需要注意以下几点:

  1. 在build方法中使用watch,在事件回调中使用read
  2. 使用select减少重建范围,只监听需要的数据
  3. 不要在initState中使用watch/read,使用didChangeDependencies或addPostFrameCallback
  4. 拆分Widget实现细粒度重建,提升性能

掌握这些状态获取方式,是灵活运用Provider的关键。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐