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,提问作者森口万太郎
相关产品推荐
相关产品推荐

