You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Riverpod官方示例中ref.read与ref.watch重建次数一致原因

关于ref.watch监听notifier不触发重建的原理解释

首先明确核心逻辑:ref.watch 触发组件重建的唯一判定标准,是你监听的目标对象的引用发生了变化,和provider内部的其他值变不变没有关系。

先搞懂StateProvider的暴露对象

示例里的StateProvider初始化完成后,会对外暴露两类可访问的对象,二者生命周期和更新逻辑完全不同:

  • 直接访问counterProvider:拿到的是当前的状态值(也就是示例里的int类型计数),每次计数更新,这个值的引用都会变化,所有监听这个值的组件会触发重建
  • 访问counterProvider.notifier:拿到的是负责修改状态的StateController<int>实例,这个对象在provider初始化完成后,只要provider没被主动销毁、刷新,整个生命周期内引用永远不会变——哪怕内部存的计数state改了无数次,notifier本身还是同一个对象

对应两个示例的行为解释

两段代码不管是用ref.read还是ref.watch拿counterProvider.notifier,操作的都是同一个不会变更的notifier实例,自然不会出现重建次数的差异:

// 不推荐写法
final counterProvider = StateProvider((ref) => 0);

Widget build(BuildContext context, WidgetRef ref) {
  StateController<int> counter = ref.read(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.state++,
    child: const Text('button'),
  );
}
// 推荐写法
final counterProvider = StateProvider((ref) => 0);

Widget build(BuildContext context, WidgetRef ref) {
  StateController<int> counter = ref.watch(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.state++,
    child: const Text('button'),
  );
}

两段代码里都没有监听会随计数变化的状态值本身,只是拿了notifier实例来触发状态修改,所以计数递增时按钮组件根本收不到变化通知,自然不会重建。

为什么官方禁止在build里用ref.read

不要误以为ref.read在这个场景下功能正常就可以用:ref.read是一次性拿值,不会建立依赖监听。如果后续加了provider刷新、自动销毁的逻辑,notifier实例真的发生变化时,用ref.read拿到的还是旧的失效实例,会直接触发空指针、状态不生效的bug。
而用ref.watch监听notifier,既不会因为内部状态变化触发无意义的重建(因为notifier引用不变),又能在notifier真的被替换时自动更新拿到最新实例,兼顾性能和正确性。

常见误区纠正:不是写了ref.watch就一定会被provider的所有状态变化连累重建,你监听什么就只响应什么的变化,监听不会变的对象就不会产生多余重建。


内容的提问来源于stack exchange,提问作者森口万太郎

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:39:43